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

84

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

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

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

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

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

Спасибо, Мэтт

Мэтт Брандт
источник

Ответы:

137

Я обнаружил, что вам вообще не нужно вручную возиться с 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, тогда новый экран будет фактически анимирован, а контроллер, который вы только что открыли, будет анимирован. Выглядит неплохо!

Алекс Уэйн
источник
молодец! гораздо лучшее решение
emmby
Потрясающе. Хотя мне не нужно было вызывать [[самосохранение] автозапуск], он все равно работает нормально.
iamj4de
4
Возможно, это очевидное дополнение, но затем вы можете поместить приведенный выше код в блок анимации для анимации перехода: [UIView beginAnimations: @ "View Flip" context: nil]; [UIView setAnimationDuration: 0.80]; [UIView setAnimationCurve: UIViewAnimationCurveEaseInOut]; [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView: кеш navController.view: НЕТ]; [navController pushViewController: анимированный newController: ДА]; [UIView commitAnimations];
Martin
11
Отлично работает с ARC, просто удалив строку сохранения / автоматического выпуска.
Ian Terrell
2
@TomerPeled Да, этому ответу почти 5 лет ... Я думаю, что это было так, как в iOS 3. API-интерфейсы изменились достаточно, чтобы я не уверен, что это лучший ответ.
Alex Wayne
56

Следующий подход мне кажется более приятным, и он также хорошо работает с 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];
Люк Роджерс
источник
1
@LukeRogers, это вызывает у меня следующее предупреждение: Завершение перехода навигации в неожиданном состоянии. Дерево вложенного представления панели навигации может быть повреждено. Есть способ подавить это?
zaitsman
Используя это решение, вы перезаписываете всплывающее окно. И для отображения в DetailView ваш код должен 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];}
выглядеть так
То, что я искал.
JERC
9

По опыту, вам придется viewControllersнапрямую возиться со свойством UINavigationController . Примерно так должно работать:

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

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

Лили Баллард
источник
7

После больших усилий (и корректировки кода Кевина) я наконец понял, как это сделать в контроллере представления, который извлекается из стека. Проблема, с которой я столкнулся, заключалась в том, что self.navigationController возвращал nil после того, как я удалил последний объект из массива контроллеров. Я думаю, это произошло из-за этой строки в документации для UIViewController в методе экземпляра navigationController «Возвращает контроллер навигации, только если контроллер представления находится в его стеке».

Я думаю, что как только текущий контроллер представления будет удален из стека, его метод navigationController вернет nil.

Вот исправленный код, который работает:

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

источник
Это дает мне черное целое!
LAOMUSIC ARTS
4

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

        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 ..

Йохан

Йохан
источник
Ваш код - это именно то, что я использовал, отлично! Благодарю. Одно замечание: с переходом загиба страницы я получил белый штрих внизу экрана (кто знает почему), но с переворотом он работал нормально. В любом случае, это красивый и компактный код!
Дэвид Х
3

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

msmq
источник
1

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

[контроллеры 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];

Mattlangtree
источник
1

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

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

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
диклофис
источник
1

Вот еще один подход, который не требует прямого вмешательства в массив 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];
}
ezekielDFM
источник
1
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
    for(int i=0;i<controllers.count;i++){
       [controllers removeLastObject];
    }
 self.navigationController.viewControllers = controllers;
Рави
источник
это вызывает у меня предупреждение в консоли - Завершение перехода навигации в неожиданном состоянии. Дерево вложенного представления панели навигации может быть повреждено. Есть способ подавить это?
zaitsman
1

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

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

jignesh
источник
0

Для монотач / 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
Набиль.А
источник
0

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

Вы можете использовать, categoryчтобы не self.navigationControllerбыть nilпослеpopViewControllerAnimated

просто нажмите и нажмите, это легко понять, доступ не требуется viewControllers....

В вашем ViewController

Payliu
источник
0

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

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

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

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

аляскер
источник
0

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

[self.navigationController pushViewController:controller animated:YES];
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newControllers removeObject:newControllers[newControllers.count - 2]];
[self.navigationController setViewControllers:newControllers];
code4j
источник