Como posso pop uma visão de um UINavigationController e substituí-lo por outro em uma operação?

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.

  1. 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.
  2. Você deve 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];