Как я могу извлечь представление из UINavigationController и заменить его другим в одной операции?
У меня есть приложение, в котором мне нужно удалить одно представление из стека UINavigationController и заменить его другим. Ситуация такова, что первое представление создает редактируемый элемент, а затем заменяет себя редактором для элемента. Когда я делаю очевидное решение в первом представлении:
MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];
[self retain];
[self.navigationController popViewControllerAnimated: NO];
[self.navigationController pushViewController: mevc animated: YES];
[self release];
Я получаю очень странное поведение. Обычно отображается окно редактора, но если я пытаюсь использовать кнопку "Назад" на панели навигации, я получаю дополнительные экраны, некоторые пустые, а некоторые просто облажались. Название становится случайным тоже. Это похоже на то, что стек навигации полностью скрыт.
Как лучше подходить к этой проблеме?
Спасибо Мэтт
16 ответов
Я обнаружил, что вам не нужно вручную связываться с viewControllers
собственность вообще. По сути, в этом есть две хитрости.
self.navigationController
вернусьnil
еслиself
в настоящее время не находится в стеке контроллера навигации. Поэтому сохраните его в локальной переменной, прежде чем потерять к нему доступ.- Вы должны
retain
(и правильноrelease
)self
или объект, которому принадлежит метод, в котором вы находитесь, будет освобожден, что приведет к странности.
Как только вы сделаете эту подготовку, просто нажмите и нажмите как обычно. Этот код мгновенно заменит верхний контроллер другим.
// 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];
В этой последней строке, если вы измените animated
в YES
, тогда новый экран будет фактически оживлен, а контроллер, который вы только что открыли, оживит. Выглядит довольно красиво!
Следующий подход кажется мне более приятным, а также хорошо работает с 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];
По опыту, вам придется возиться с UINavigationController's viewControllers
собственность напрямую. Примерно так должно работать:
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];
Примечание. Я изменил сохранение / освобождение на сохранение / автоматическое освобождение, так как оно обычно более устойчивое - если между сохранением / выпуском возникает исключение, вы утечете себя, но автоматическое освобождение позаботится об этом.
После долгих усилий (и настройки кода от Кевина) я наконец понял, как это сделать в контроллере представления, который извлекается из стека. У меня была проблема с тем, что self.navigationController возвращал ноль после того, как я удалил последний объект из массива контроллеров. Я думаю, что это произошло из-за этой строки в документации для UIViewController для метода экземпляра navigationController "Возвращает контроллер навигации, только если контроллер представления находится в своем стеке".
Я думаю, что, как только текущий контроллер представления удален из стека, его метод navigationController возвратит ноль.
Вот исправленный код, который работает:
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];
Спасибо, это было именно то, что мне было нужно. Я также поместил это в анимацию, чтобы получить завиток страницы:
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 - это быстро, хорошо для 3GS и новее, 0,8 - слишком быстро для 3G.
Johan
Если вы хотите показать любой другой контроллер представления с помощью popToRootViewController, то вам нужно сделать следующее:
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];
Теперь весь ваш предыдущий стек будет удален, и новый стек будет создан с вашим требуемым rootViewController.
Недавно мне пришлось сделать нечто подобное, и мое решение было основано на ответе Майклза. В моем случае мне пришлось удалить два контроллера просмотра из стека навигации, а затем добавить новый контроллер просмотра. призвание
[controllers removeLastObject];дважды работал нормально в моем случае.
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];
Мой любимый способ сделать это с помощью категории на UINavigationController. Следующее должно работать:
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
Затем из вашего контроллера вида вы можете заменить вид сверху новым:
[self.navigationController replaceTopViewControllerWithViewController: newController];
Вот еще один подход, который не требует прямого взаимодействия с массивом viewControllers. Проверьте, был ли контроллер выдвинут, если это так, нажмите его.
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<controllers.count;i++){
[controllers removeLastObject];
}
self.navigationController.viewControllers = controllers;
Это UINavigationController
метод экземпляра может работать...
Вызывает контроллеры представления, пока указанный контроллер представления не является контроллером вида сверху, а затем обновляет отображение.
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
С другой стороны,
Ты можешь использовать category
избежать self.navigationController
быть nil
после popViewControllerAnimated
просто нажмите и нажмите, это легко понять, не требуется доступ 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
В вашем ViewController
// #import "UINavigationController+Helper.h"
// invoke in your code
UIViewController *v= [[MyNewViewController alloc] init];
[self.navigationController popThenPushViewController:v animated:YES];
RELEASE_SAFELY(v);
Я использую это решение, чтобы сохранить анимацию.
[self.navigationController pushViewController:controller animated:YES];
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newControllers removeObject:newControllers[newControllers.count - 2]];
[self.navigationController setViewControllers:newControllers];
Для однотонного / ксамаринового IOS:
внутри класса UISplitViewController;
UINavigationController mainNav = this._navController;
//List<UIViewController> controllers = mainNav.ViewControllers.ToList();
mainNav.ViewControllers = new UIViewController[] { };
mainNav.PushViewController(detail, true);//to have the animation
Не совсем ответ, но может помочь в некоторых сценариях (например, мой):
Если вам нужно вывести viewcontroller C и перейти к B (вне стека) вместо A (один ниже C), можно нажать B перед C и иметь все 3 в стеке. Держа нажатие кнопки B невидимым и выбирая, следует ли использовать только C или C и B, вы можете добиться того же эффекта.
Начальная проблема A -> C (я хочу, чтобы вытолкнуть C и показать B, из стека)
возможное решение A -> B (выдвинуто невидимым) -> C (когда я выскакиваю C, я выбираю показать B или также выскакиваю это)
С помощью массива контроллеров представления навигации вы можете проверить все контроллеры представления, добавленные в стек навигации. Используя этот массив, вы можете вернуться назад к определенному контроллеру представления.