Как я могу извлечь представление из UINavigationController и заменить его другим в одной операции?

У меня есть приложение, в котором мне нужно удалить одно представление из стека UINavigationController и заменить его другим. Ситуация такова, что первое представление создает редактируемый элемент, а затем заменяет себя редактором для элемента. Когда я делаю очевидное решение в первом представлении:

MyEditViewController *mevc = [[MYEditViewController alloc] initWithGizmo: gizmo];

[self retain];
[self.navigationController popViewControllerAnimated: NO];
[self.navigationController pushViewController: mevc animated: YES];
[self release];

Я получаю очень странное поведение. Обычно отображается окно редактора, но если я пытаюсь использовать кнопку "Назад" на панели навигации, я получаю дополнительные экраны, некоторые пустые, а некоторые просто облажались. Название становится случайным тоже. Это похоже на то, что стек навигации полностью скрыт.

Как лучше подходить к этой проблеме?

Спасибо Мэтт

16 ответов

Я обнаружил, что вам не нужно вручную связываться с viewControllers собственность вообще. По сути, в этом есть две хитрости.

  1. self.navigationController вернусь nil если self в настоящее время не находится в стеке контроллера навигации. Поэтому сохраните его в локальной переменной, прежде чем потерять к нему доступ.
  2. Вы должны 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 или также выскакиваю это)

С помощью массива контроллеров представления навигации вы можете проверить все контроллеры представления, добавленные в стек навигации. Используя этот массив, вы можете вернуться назад к определенному контроллеру представления.

Другие вопросы по тегам