Як я можу вивести подання з 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власністю. В основному в цьому є 2 хитрі речі.

  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 cache: NO]; [navController pushViewController: newController анімований: ТАК]; [UIView commitAnimations];
Мартін

11
Чудово працює з ARC, просто видаливши лінію збереження / автоматичного випуску.
Ян Террелл,

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

Примітка: Я змінив retain / release на retain / autorelease, оскільки це, як правило, є більш надійним - якщо між збереженням / випуском виникає виняток, ви випустите самостійно, але autorelease подбає про це.


7

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

Це дає мені чорне ціле!
ЛАОМУЗИЧНЕ МИСТЕЦТВО

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

Йохан


Ваш код - саме те, що я використовував, чудово! Дякую. Одне зауваження: з переходом на скручування сторінки я отримав білу штуку внизу зору (хто знає чому), але з перевертанням це спрацювало нормально. У будь-якому випадку, це приємний і компактний код!
David H

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.


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


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

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

0

Ви можете перевірити масив контролерів перегляду навігації, який ви надаєте всім контролерам перегляду, доданим у стек навігації. Використовуючи цей масив, ви можете повернутися до певного контролера перегляду.


0

Для IOS 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....

// 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);

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];
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.