84 votes

Comment puis-je faire éclater une vue d'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 pile d'un UINavigationController et la remplacer par une autre. La situation est que la première vue crée un élément éditable 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];

Je rencontre un comportement très étrange. En général, 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 vides, et certains juste gâchés. Le titre devient aléatoire aussi. C'est comme si la pile de navigation était complètement corrompue.

Quelle serait une meilleure approche pour ce problème?

Merci, Matt

137voto

Alex Wayne Points 58113

J'ai découvert que vous n'avez pas besoin de manipuler manuellement la propriété viewControllers du tout. En gros, il y a 2 choses délicates à ce sujet.

  1. self.navigationController renverra nil si self n'est pas actuellement dans la pile du contrôleur de navigation. Donc, enregistrez-le dans une variable locale avant de perdre l'accès.
  2. Vous devez retain (et liberer correctement) self ou l'objet qui possède la méthode dans laquelle vous vous trouvez sera désalloué, causant des anomalies.

Une fois que vous avez préparé cela, faites simplement un pop et push comme d'habitude. Ce code remplace instantanément le contrôleur supérieur par un autre.

// stocker localement le contrôleur de navigation car
// self.navigationController sera nil une fois que nous aurons été popped
UINavigationController *navController = self.navigationController;

// nous retenir pour que le contrôleur existe toujours une fois qu'il est poppé
[[self retain] autorelease];

// Pop ce contrôleur et le remplacer par un autre
[navController popViewControllerAnimated:NO];
[navController pushViewController:someViewController animated:NO];

Dans la dernière ligne, si vous changez le animated en YES, alors la nouvelle écran animera effectivement et le contrôleur que vous venez de popper animera dehors. Ça a l'air plutôt sympa !

0 votes

Génial! Beaucoup meilleure solution

0 votes

Génial. Bien que je n'aie pas besoin d'appeler [[self retain] autorelease], cela fonctionne toujours bien.

0 votes

Intéressant, mon problème semblait être causé par le fait d'avoir défini popViewControllerAnimated sur oui. Je l'ai donc mis sur non et laissé le push sur oui, et tout semble bien maintenant! Santé.

56voto

Luke Rogers Points 445

L'approche suivante me semble plus agréable, et fonctionne bien avec ARC:

UIViewController *newVC = [[UIViewController alloc] init];
// Remplacer le contrôleur de vue actuel
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[[self navigationController] viewControllers]];
[viewControllers removeLastObject];
[viewControllers addObject:newVC];
[[self navigationController] setViewControllers:viewControllers animated:YES];

1 votes

@LukeRogers, cela provoque l'avertissement suivant pour moi : Finir une transition de navigation dans un état inattendu. L'arborescence des sous-vues de la barre de navigation pourrait être corrompue. Y a-t-il un moyen de le supprimer?

0 votes

En utilisant cette solution, vous écrasez le popover. Et pour l'afficher dans la DetailView, votre code devrait lire : if(indexPath.row == 0){UIViewController *newVC = [[UIViewController alloc] init];newVC = [self.storyboard instantiateViewControllerWithIdentifier:@"Item1VC"]; NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:[_detailViewController.navigationController viewControllers]]; [viewControllers removeLastObject];[viewControllers addObject:newVC]; [_detailViewController.navigationController setViewControllers:viewControllers animated:YES];}

0 votes

Ce que je cherchais.

9voto

Kevin Ballard Points 88866

De mon expérience, vous allez devoir manipuler directement la propriété viewControllers du UINavigationController. 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];

Note : J'ai changé le retain/release en un retain/autorelease car c'est généralement plus robuste - si une exception se produit entre le retain/release, vous allez faire une fuite mémoire de self, mais autorelease s'occupe de cela.

7voto

Après beaucoup d'efforts (et en ajustant le code de Kevin), j'ai finalement réussi à comprendre comment faire cela dans le contrôleur de vue qui est extrait de la pile. Le problème que j'avais était que self.navigationController renvoyait nil après avoir supprimé le dernier objet du tableau des contrôleurs. Je pense que cela était dû à cette ligne dans la documentation pour UIViewController sur la méthode d'instance navigationController "Renvoie uniquement un contrôleur de navigation si le contrôleur de vue est dans sa pile."

Je pense qu'une fois que le contrôleur de vue actuel est retiré de la pile, sa méthode navigationController renverra nil.

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

0 votes

Cela me donne un trou noir !

4voto

Johan Points 41

Merci, c'était exactement ce dont j'avais besoin. J'ai également mis cela dans une animation pour obtenir le retour de page :

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

La durée de 0,6 est rapide, idéale pour le 3GS et les modèles plus récents, 0,8 est encore un peu trop rapide pour le 3G..

Johan

0 votes

Votre code est exactement ce que j'ai utilisé, super ! Merci. Une remarque : avec la transition de la page de curl, j'ai un artefact blanc en bas de la vue (qui sait pourquoi) mais avec le flip ça a bien fonctionné. Quoi qu'il en soit, c'est un code agréable et compact !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X