Mam aplikację, w której muszę usunąć jeden widok ze stosu UINavigationController i zastąpić go innym. Sytuacja jest taka, że pierwszy widok tworzy element edytowalny, a następnie zastępuje się edytorem elementu. Kiedy robię oczywiste rozwiązanie w pierwszym widoku:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];
[self retain];
[self.navigationController popViewControllerAnimated: NO];
[self.navigationController pushViewController: mevc animated: YES];
[self release];
Zachowuje się bardzo dziwnie. Zwykle pojawia się widok edytora, ale jeśli spróbuję użyć przycisku Wstecz na pasku nawigacyjnym, pojawiają się dodatkowe ekrany, niektóre puste, a niektóre po prostu schrzanione. Tytuł też staje się przypadkowy. To tak, jakby stos nawigacji był całkowicie zawiązany.
Jakie byłoby lepsze podejście do tego problemu?
Dzięki, Matt
źródło
Poniższe podejście wydaje mi się przyjemniejsze i działa również dobrze z 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];
źródło
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];}
Z doświadczenia wynika, że będziesz musiał
viewControllers
bezpośrednio bawić się własnością UINavigationControllera . Coś takiego powinno działać: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];
Uwaga: Zmieniłem zachowanie / zwolnienie na zachowaj / zwolnij, ponieważ jest to po prostu bardziej niezawodne - jeśli wystąpi wyjątek między zatrzymaniem / zwolnieniem, wyciekniesz samoczynnie, ale autorelease się tym zajmą.
źródło
Po wielu wysiłkach (i poprawieniu kodu od Kevina) w końcu wymyśliłem, jak to zrobić w kontrolerze widoku, który jest zdejmowany ze stosu. Problem, który miałem, polegał na tym, że self.navigationController zwracało zero po usunięciu ostatniego obiektu z tablicy controllers. Myślę, że było to spowodowane tym wierszem w dokumentacji dla UIViewController w metodzie wystąpienia navigationController „Zwraca kontroler nawigacji tylko wtedy, gdy kontroler widoku jest na stosie”.
Myślę, że gdy bieżący kontroler widoku zostanie usunięty ze stosu, jego metoda navigationController zwróci nil.
Oto dostosowany kod, który działa:
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];
źródło
Dzięki, właśnie tego potrzebowałem. Umieściłem to również w animacji, aby uzyskać zawinięcie strony:
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];
Czas trwania 0.6 jest szybki, dobry dla 3GS i nowszych, 0.8 to wciąż trochę za szybko dla 3G.
Johan
źródło
Jeśli chcesz wyświetlić inny kontroler widoku przez popToRootViewController, musisz wykonać następujące czynności:
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];
Teraz cały twój poprzedni stos zostanie usunięty, a nowy stos zostanie utworzony z wymaganym rootViewController.
źródło
Ostatnio musiałem zrobić coś podobnego i oparłem swoje rozwiązanie na odpowiedzi Michaelsa. W moim przypadku musiałem usunąć dwa kontrolery widoku ze stosu nawigacji, a następnie dodać nowy kontroler widoku. Powołanie
dwa razy, działało dobrze w moim przypadku.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];
źródło
Ta
UINavigationController
metoda instancji może działać ...Wyświetla kontrolery widoku do momentu, gdy określony kontroler widoku stanie się kontrolerem widoku z góry, a następnie zaktualizuje wyświetlacz.
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
źródło
Oto inne podejście, które nie wymaga bezpośredniego mieszania się z tablicą viewControllers. Sprawdź, czy kontroler został już wyskakujący, jeśli tak, naciśnij go.
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]; }
źródło
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy]; for(int i=0;i<controllers.count;i++){ [controllers removeLastObject]; } self.navigationController.viewControllers = controllers;
źródło
Moim ulubionym sposobem jest dodanie kategorii na UINavigationController. Powinno działać:
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
Następnie z kontrolera widoku możesz zastąpić widok z góry nowym, wykonując następujące czynności:
[self.navigationController replaceTopViewControllerWithViewController: newController];
źródło
Możesz sprawdzić za pomocą tablicy kontrolerów widoku nawigacji, którą dajesz wszystkie kontrolery widoku, które dodałeś w stosie nawigacji. Używając tej tablicy, możesz z powrotem przejść do określonego kontrolera widoku.
źródło
W przypadku monotouch / Xamarin IOS:
wewnątrz klasy UISplitViewController;
UINavigationController mainNav = this._navController; //List<UIViewController> controllers = mainNav.ViewControllers.ToList(); mainNav.ViewControllers = new UIViewController[] { }; mainNav.PushViewController(detail, true);//to have the animation
źródło
Alternatywnie,
Możesz użyć,
category
aby uniknąćself.navigationController
byćnil
popopViewControllerAnimated
po prostu pop i naciśnij, jest to łatwe do zrozumienia, nie trzeba uzyskiwać dostępu
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
W Twoim ViewController
// #import "UINavigationController+Helper.h" // invoke in your code UIViewController *v= [[MyNewViewController alloc] init]; [self.navigationController popThenPushViewController:v animated:YES]; RELEASE_SAFELY(v);
źródło
Nie do końca odpowiedź, ale może być pomocna w niektórych scenariuszach (na przykład mój):
Jeśli chcesz zdjąć kontroler widoku C i przejść do B (poza stosem) zamiast A (ten poniżej C), możesz wcisnąć B przed C i mieć wszystkie 3 na stosie. Utrzymując przycisk B niewidocznym, i wybierając, czy wyskakuje tylko C, czy C i B razem, możesz osiągnąć ten sam efekt.
początkowy problem A -> C (chcę zdjąć C i pokazać B, poza stosem)
możliwe rozwiązanie A -> B (wciśnięte niewidoczne) -> C (kiedy wyskakuję C, wybieram pokazanie B lub też pęknięcie)
źródło
Używam tego rozwiązania do zachowania animacji.
[self.navigationController pushViewController:controller animated:YES]; NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers]; [newControllers removeObject:newControllers[newControllers.count - 2]]; [self.navigationController setViewControllers:newControllers];
źródło