Comment puis-je afficher une vue depuis un UINavigationController et la remplacer par une autre en une seule opération?

J’ai une application où je dois supprimer une vue de la stack d’un UINavigationController et la remplacer par une autre. La situation est que la première vue crée un élément modifiable, puis se remplace par un éditeur pour l’élément. Lorsque je fais la solution évidente dans la première vue:

MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo]; [self retain]; [self.navigationController popViewControllerAnimated: NO]; [self.navigationController pushViewController: mevc animated: YES]; [self release]; 

J’ai un comportement très étrange. Habituellement, la vue de l’éditeur apparaîtra, mais si j’essaie d’utiliser le bouton Précédent de la barre de navigation, j’obtiens des écrans supplémentaires, certains en blanc et d’autres juste foutus. Le titre devient aléatoire aussi. C’est comme si la stack de nav est complètement arrosée.

Quelle serait une meilleure approche à ce problème?

Merci, Matt

    J’ai découvert que vous n’avez pas besoin de viewControllers manuellement la propriété viewControllers . Fondamentalement, il y a 2 choses délicates à ce sujet.

    1. self.navigationController renvoie nil si self n’est pas actuellement dans la stack du contrôleur de navigation. Enregistrez-le donc dans une variable locale avant de pouvoir y accéder.
    2. Vous devez retain (et release correctement) vous- self ou l’object qui possède la méthode dans laquelle vous vous trouvez sera libéré, provoquant l’étrangeté.

    Une fois que vous avez fait cette préparation, il vous suffit de sauter et de pousser comme d’habitude. Ce code remplacera instantanément le contrôleur supérieur par un autre.

     // 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]; 

    Dans cette dernière ligne, si vous modifiez l’ animated sur YES , le nouvel écran s’animera et le contrôleur que vous venez de lancer s’animera. Ça a l’air bien sympa!

    L’approche suivante me semble plus intéressante et fonctionne bien avec 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]; 

    D’expérience, vous devrez viewControllers directement la propriété viewControllers de viewControllers . Quelque chose comme ça devrait fonctionner:

     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]; 

    Remarque: J’ai modifié la gestion des retenues / des versions en une version à conserver / autorelease, car elle est généralement plus robuste – si une exception se produit entre la maintenance et la publication, vous vous ferez une fuite, mais autorelease s’en chargera.

    Après beaucoup d’efforts (et de peaufiner le code de Kevin), j’ai finalement compris comment faire cela dans le contrôleur de vue qui est sorti de la stack. Le problème que je rencontrais était que self.navigationController retournait nul après avoir supprimé le dernier object du tableau des contrôleurs. Je pense que c’était dû à cette ligne dans la documentation de UIViewController sur la méthode d’instance navigationController “Renvoie uniquement un contrôleur de navigation si le contrôleur de vue est dans sa stack.”

    Je pense qu’une fois que le contrôleur de vue actuel est retiré de la stack, sa méthode navigationController renvoie zéro.

    Voici le code ajusté qui fonctionne:

     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]; 

    Merci, c’était exactement ce dont j’avais besoin. Je mets aussi ça dans une animation pour obtenir la page curl:

      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 durée est rapide, bon pour 3GS et plus récent, 0.8 est encore un peu trop rapide pour la 3G.

    Johan

    Si vous souhaitez afficher un autre contrôleur de vue par popToRootViewController, vous devez suivre les étapes suivantes:

      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]; 

    Maintenant, toute votre stack précédente sera supprimée et une nouvelle stack sera créée avec votre rootViewController requirejs.

    J’ai dû faire une chose similaire récemment et j’ai basé ma solution sur la réponse de Michaels. Dans mon cas, j’ai dû retirer deux contrôleurs de vue de la stack de navigation, puis append un nouveau contrôleur de vue. Appel

      [contrôleurs removeLastObject]; 

    deux fois, a bien fonctionné dans mon cas.

     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]; 

    Cette méthode d’instance UINavigationController pourrait fonctionner …

    Affiche les contrôleurs jusqu’à ce que le contrôleur de vue spécifié soit le contrôleur de la vue supérieure, puis met à jour l’affichage.

     - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated 

    Voici une autre approche qui ne nécessite pas de jouer directement avec le tableau viewControllers. Vérifiez si le contrôleur a déjà été installé, si tel est le cas, poussez-le.

     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 

    Ma façon préférée de le faire est d’utiliser une catégorie sur UINavigationController. Les éléments suivants devraient fonctionner:

    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 

    Ensuite, à partir de votre contrôleur de vue, vous pouvez remplacer la vue de dessus par une nouvelle par comme ceci:

     [self.navigationController replaceTopViewControllerWithViewController: newController]; 

    Vous pouvez vérifier avec le tableau de bord de la navigation les contrôleurs que vous avez tous les contrôleurs de vue que vous avez ajoutés dans la stack de navigation. En utilisant ce tableau, vous pouvez naviguer vers un contrôleur de vue spécifique.

    Pour monotouch / xamarin IOS:

    dans la classe UISplitViewController;

     UINavigationController mainNav = this._navController; //List controllers = mainNav.ViewControllers.ToList(); mainNav.ViewControllers = new UIViewController[] { }; mainNav.PushViewController(detail, true);//to have the animation 

    Alternativement,

    Vous pouvez utiliser category pour éviter que self.navigationController soit nil après popViewControllerAnimated

    juste pop et push, c’est facile à comprendre, pas besoin d’accéder à 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 

    Dans votre ViewController

     // #import "UINavigationController+Helper.h" // invoke in your code UIViewController *v= [[MyNewViewController alloc] init]; [self.navigationController popThenPushViewController:v animated:YES]; RELEASE_SAFELY(v); 

    Pas exactement la réponse mais pourrait être utile dans certains scénarios (le mien par exemple):

    Si vous avez besoin de pop viewcontroller C et d’aller à B (hors stack) au lieu de A (celui ci-dessous C), il est possible de pousser B avant C et d’avoir les 3 sur la stack. En gardant la poussée B invisible et en choisissant de ne mettre que C ou C et B, vous pouvez obtenir le même effet.

    problème initial A -> C (je veux faire apparaître C et montrer B, hors stack)

    solution possible A -> B (invisible invisible) -> C (quand je fais C, je choisis de montrer B ou de le faire apparaître)

    J’utilise cette solution pour conserver l’animation.

     [self.navigationController pushViewController:controller animated:YES]; NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers]; [newControllers removeObject:newControllers[newControllers.count - 2]]; [self.navigationController setViewControllers:newControllers];