Eu tenho um aplicativo onde eu preciso remover uma visão da pilha de um UINavigationController e substituí-lo por outro. A situação é que a primeira visualização cria um item editável e, em seguida, substitui-se por um editor para o item. Quando faço a solução óbvia na primeira vista:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo]; [self retain]; [self.navigationController popViewControllerAnimated: NO]; [self.navigationController pushViewController: mevc animated: YES]; [self release];
Eu tenho um comportamento muito estranho. Normalmente, a visualização do editor será exibida, mas se eu tentar usar o botão Voltar na barra de navegação, recebo canvass extras, algumas em branco e outras apenas distorcidas. O título se torna random também. É como se a pilha de naves fosse completamente lavada.
Qual seria uma abordagem melhor para esse problema?
Obrigado Matt
Eu descobri que você não precisa mexer manualmente com a propriedade viewControllers
. Basicamente, existem duas coisas complicadas sobre isso.
self.navigationController
retornará nil
se self
não estiver atualmente na pilha do controlador de navegação. Então, salve-o em uma variável local antes de perder o access a ela. retain
(e release
adequadamente) ou o object que possui o método em que você está será desalocado, causando estranheza. Depois de fazer essa preparação, basta estourar e empurrar normalmente. Este código irá replace instantaneamente o controlador superior por outro.
// locally store the navigation controller since // self.navigationController will be nil once we are popped UINavigationController *navController = self.navigationController; // retain ourselves so that the controller will still exist once it's popped off [[self retain] autorelease]; // Pop this controller and replace with another [navController popViewControllerAnimated:NO]; [navController pushViewController:someViewController animated:NO];
Nessa última linha, se você alterar o animated
para YES
, a nova canvas será realmente animada e o controlador que você acabou de exibir se animará. Parece muito legal!
A abordagem a seguir parece melhor para mim e também funciona bem com o ARC:
UIViewController *newVC = [[UIViewController alloc] init]; // Replace the current view controller NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]]; [viewControllers removeLastObject]; [viewControllers addObject:newVC]; [[self navigationController] setViewControllers:viewControllers animated:YES];
Por experiência, você terá que mexer diretamente com a propriedade viewControllers
do UINavigationController. Algo como isso deve funcionar:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo]; [[self retain] autorelease]; NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease]; [controllers removeLastObject]; self.navigationController.viewControllers = controllers; [self.navigationController pushViewController:mevc animated: YES];
Nota: Eu mudei a retenção / liberação para uma retenção / autorelease, pois isso geralmente é mais robusto – se ocorrer uma exceção entre a retenção / liberação, você vai vazar, mas o autorelease cuida disso.
Depois de muito esforço (e aprimorando o código de Kevin), finalmente descobri como fazer isso no controlador de exibição que está sendo retirado da pilha. O problema que eu estava tendo era que self.navigationController estava retornando nil depois que eu removi o último object do array controllers. Eu acho que foi devido a esta linha na documentação do UIViewController no método de instância navigationController “Só retorna um controlador de navegação se o controlador de visualização está em sua pilha.”
Eu acho que uma vez que o controlador de visão atual é removido da pilha, seu método navigationController retornará nulo.
Aqui está o código ajustado que funciona:
UINavigationController *navController = self.navigationController; MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo]; NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease]; [controllers removeLastObject]; navController.viewControllers = controllers; [navController pushViewController:mevc animated: YES];
Obrigado, isso foi exatamente o que eu precisava. Eu também coloco isso em uma animação para obter a ondulação da página:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo]; UINavigationController *navController = self.navigationController; [[self retain] autorelease]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration: 0.7]; [UIView setAnimationTransition:<#UIViewAnimationTransitionCurlDown#> forView:navController.view cache:NO]; [navController popViewControllerAnimated:NO]; [navController pushViewController:mevc animated:NO]; [UIView commitAnimations];
0,6 duração é rápida, boa para 3GS e mais recente, 0,8 ainda é um pouco rápido demais para 3G ..
Johan
Se você quiser mostrar qualquer outro controlador de visualização por popToRootViewController, então você precisa fazer o seguinte:
UIViewController *newVC = [[WelcomeScreenVC alloc] initWithNibName:@"WelcomeScreenVC" bundle:[NSBundle mainBundle]]; NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]]; [viewControllers removeAllObjects]; [viewControllers addObject:newVC]; [[self navigationController] setViewControllers:viewControllers animated:NO];
Agora, toda a sua pilha anterior será removida e uma nova pilha será criada com o rootViewController necessário.
Eu tive que fazer uma coisa semelhante recentemente e basear minha solução na resposta de Michaels. No meu caso eu tive que remover dois Controladores de Visualização da Pilha de Navegação e adicionar um novo Controlador de Visão. Chamando
[controllers removeLastObject];
duas vezes, funcionou bem no meu caso.
UINavigationController *navController = self.navigationController; // retain ourselves so that the controller will still exist once it's popped off [[self retain] autorelease]; searchViewController = [[SearchViewController alloc] init]; NSMutableArray *controllers = [[self.navigationController.viewControllers mutableCopy] autorelease]; [controllers removeLastObject]; // In my case I want to go up two, then push one.. [controllers removeLastObject]; navController.viewControllers = controllers; NSLog(@"controllers: %@",controllers); controllers = nil; [navController pushViewController:searchViewController animated: NO];
Esse método de instância do UINavigationController
pode funcionar …
Pops visualiza os controladores até que o controlador de visualização especificado seja o controlador de vista superior e, em seguida, atualize a exibição.
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
Aqui está uma outra abordagem que não requer diretamente mexer com o array viewControllers. Verifique se o controlador foi pop’d ainda, se assim for, empurre-o.
TasksViewController *taskViewController = [[TasksViewController alloc] initWithNibName:nil bundle:nil]; if ([navigationController.viewControllers indexOfObject:taskViewController] == NSNotFound) { [navigationController pushViewController:taskViewController animated:animated]; } else { [navigationController popToViewController:taskViewController animated:animated]; }
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy]; for(int i=0;i
Minha maneira favorita de fazer isso é com uma categoria no UINavigationController. O seguinte deve funcionar:
UINavigationController + Helpers.h #import
@interface UINavigationController (Helpers) - (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller; @end
UINavigationController + Helpers.m
#import “UINavigationController + Helpers.h”
@implementation UINavigationController (Helpers) - (UIViewController*) replaceTopViewControllerWithViewController: (UIViewController*) controller { UIViewController* topController = self.viewControllers.lastObject; [[topController retain] autorelease]; UIViewController* poppedViewController = [self popViewControllerAnimated:NO]; [self pushViewController:controller animated:NO]; return poppedViewController; } @end
Então, a partir do seu controlador de visualização, você pode replace a vista superior por uma nova assim:
[self.navigationController replaceTopViewControllerWithViewController: newController];
Você pode verificar com o array de controladores de visualização de navegação que você fornece a todos os controladores de visualização que você adicionou na pilha de navegação. Usando essa matriz, você pode voltar a navegar para o controlador de exibição específico.
Para monotouch / xamarin IOS:
dentro da class UISplitViewController;
UINavigationController mainNav = this._navController; //List controllers = mainNav.ViewControllers.ToList(); mainNav.ViewControllers = new UIViewController[] { }; mainNav.PushViewController(detail, true);//to have the animation
Alternativamente,
Você pode usar category
para evitar que self.navigationController
seja nil
após popViewControllerAnimated
apenas pop e push, é fácil de entender, não precisa acessar viewControllers
….
// UINavigationController+Helper.h @interface UINavigationController (Helper) - (UIViewController*) popThenPushViewController:(UIViewController *)viewController animated:(BOOL)animated; @end // UINavigationController+Helper.m @implementation UINavigationController (Helper) - (UIViewController*) popThenPushViewController:(UIViewController *)viewController animated:(BOOL)animated { UIViewController *v =[self popViewControllerAnimated:NO]; [self pushViewController:viewController animated:animated]; return v; } @end
No seu ViewController
// #import "UINavigationController+Helper.h" // invoke in your code UIViewController *v= [[MyNewViewController alloc] init]; [self.navigationController popThenPushViewController:v animated:YES]; RELEASE_SAFELY(v);
Não é exatamente a resposta, mas pode ajudar em alguns cenários (o meu, por exemplo):
Se você precisar popcontrar o controlador de visualização C e ir para B (fora da pilha) em vez de A (o que está abaixo de C), é possível pressionar B antes de C e ter todos os 3 na pilha. Mantendo o botão B invisível, e escolhendo entre popar C ou C e B, você pode obter o mesmo efeito.
problema inicial A -> C (eu quero estourar C e mostrar B, fora da pilha)
solução possível A -> B (empurrado invisível) -> C (quando eu pop C, eu escolho mostrar B ou também pop)
Eu uso essa solução para manter a animação.
[self.navigationController pushViewController:controller animated:YES]; NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers]; [newControllers removeObject:newControllers[newControllers.count - 2]]; [self.navigationController setViewControllers:newControllers];