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

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

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

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

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

Что было бы лучшим подходом к этой проблеме?

Спасибо, Matt

Ответ 1

Я обнаружил, что вам не нужно вручную связываться с свойством 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 popped off
[[self retain] autorelease];

// Pop this controller and replace with another
[navController popViewControllerAnimated:NO];
[navController pushViewController:someViewController animated:NO];

В этой последней строке, если вы измените animated на YES, тогда новый экран будет на самом деле оживлен, и только что вы нажали контроллер. Выглядит неплохо!

Ответ 2

Следующий подход кажется мне более приятным, а также хорошо работает с 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];

Ответ 3

По опыту вам нужно будет напрямую играть с свойством UINavigationController 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];

Примечание. Я изменил сохранение/освобождение на сохранение/авторекламу, поскольку это обычно более устойчиво - если возникает исключение между сохранением/выпуском, которое вы будете утечка себя, но autorelease позаботится об этом.

Ответ 4

После долгих усилий (и настройки кода от Кевина) я, наконец, понял, как это сделать в контроллере представления, который выталкивается из стека. Проблема, с которой я столкнулась, заключалась в том, что 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];

Ответ 5

Спасибо, это было именно то, что мне нужно. Я также помещал это в анимацию, чтобы получить завиток страницы:

        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.

Йохан

Ответ 6

Если вы хотите показать любой другой контроллер вида 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.

Ответ 7

Этот метод экземпляра UINavigationController может работать...

Контролирует контроллеры просмотра до тех пор, пока указанный контроллер представления не будет контроллером верхнего уровня, а затем обновит отображение.

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

Ответ 8

В последнее время мне пришлось сделать что-то похожее и основали мое решение на ответе Майклса. В моем случае мне пришлось удалить два контроллера просмотра из стека навигации, а затем добавить новый контроллер просмотра. Вызов

[controllers removeLastObject];
дважды, отлично работал в моем случае.

UINavigationController *navController = self.navigationController;

// retain ourselves so that the controller will still exist once it 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];

код >

Ответ 9

Вот еще один подход, который не требует прямого взаимодействия с массивом 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];
}

Ответ 10

NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
    for(int i=0;i<controllers.count;i++){
       [controllers removeLastObject];
    }
 self.navigationController.viewControllers = controllers;

Ответ 11

Мой любимый способ сделать это с категорией в 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];

Ответ 12

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

Ответ 13

Для monotouch/xamarin IOS:

внутри класса UISplitViewController;

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

Ответ 14

В качестве альтернативы,

Вы можете использовать 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);

Ответ 15

Не совсем ответ, но может помочь в некоторых сценариях (например, мой):

Если вам нужен pop viewcontroller C и перейти к B (из стека) вместо A (один ниже C), можно нажать B до C и иметь все 3 в стеке. Сохраняя невидимый B-эффект, и, выбирая только то, чтобы поместить только C или C и B, вы можете добиться того же эффекта.

начальная проблема A → C (я хочу поместить C и показать B, из стека)

возможное решение A → B (нажата невидимая) → C (когда я pop C, я выбираю, чтобы показать B или также поп его)

Ответ 16

Я использую это решение для сохранения анимации.

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