84

我有一个应用程序,我需要从 UINavigationController 的堆栈中删除一个视图并将其替换为另一个视图。情况是第一个视图创建了一个可编辑的项目,然后用该项目的编辑器替换了它自己。当我在第一个视图中执行明显的解决方案时:

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

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

我得到非常奇怪的行为。通常会出现编辑器视图,但如果我尝试使用导航栏上的后退按钮,我会得到额外的屏幕,有些是空白的,有些只是搞砸了。标题也变得随机。就像导航堆栈完全被冲洗了一样。

解决这个问题的更好方法是什么?

谢谢,马特

4

16 回答 16

137

I've discovered you don't need to manually mess with the viewControllers property at all. Basically there are 2 tricky things about this.

  1. self.navigationController will return nil if self is not currently on the navigation controller's stack. So save it to a local variable before you lose access to it.
  2. You must retain (and properly release) self or the object who owns the method you are in will be deallocated, causing strangeness.

Once you do that prep, then just pop and push as normal. This code will instantly replace the top controller with another.

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

In that last line if you change the animated to YES, then the new screen will actually animate in and the controller you just popped will animate out. Looks pretty nice!

于 2009-11-24T01:59:36.797 回答
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];
于 2012-01-04T11:11:25.513 回答
9

根据经验,您将不得不直接摆弄 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];

注意:我将保留/释放更改为保留/自动释放,因为这通常更健壮 - 如果保留/释放之间发生异常,您将泄漏自我,但自动释放会处理这一点。

于 2009-01-04T23:57:09.433 回答
7

After much effort (and tweaking the code from Kevin), I finally figured out how to do this in the view controller that is being popped from the stack. The problem that I was having was that self.navigationController was returning nil after I removed the last object from the controllers array. I think it was due to this line in the documentation for UIViewController on the instance method navigationController "Only returns a navigation controller if the view controller is in its stack."

I think that once the current view controller is removed from the stack, its navigationController method will return nil.

Here is the adjusted code that works:

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];
于 2009-01-10T01:10:31.443 回答
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 来说还是有点太快了。

约翰

于 2010-06-23T14:50:51.903 回答
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 创建新堆栈。

于 2014-01-08T13:20:14.963 回答
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];

于 2011-03-24T04:06:50.763 回答
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];
于 2013-04-22T20:15:46.197 回答
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];
}
于 2012-06-01T14:36:26.643 回答
1

UINavigationController实例方法可能有效...

弹出视图控制器,直到指定的视图控制器是顶视图控制器,然后更新显示。

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
于 2009-01-04T05:03:16.303 回答
1
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
    for(int i=0;i<controllers.count;i++){
       [controllers removeLastObject];
    }
 self.navigationController.viewControllers = controllers;
于 2012-11-23T07:18:17.220 回答
0

您可以检查导航视图控制器数组,该数组为您提供了在导航堆栈中添加的所有视图控制器。通过使用该数组,您可以返回导航到特定的视图控制器。

于 2012-09-25T11:15:49.067 回答
0

或者,

你可以category用来避免self.navigationControllernilpopViewControllerAnimated

只需弹出和推送,很容易理解,不需要访问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

在您的视图控制器中

// #import "UINavigationController+Helper.h"
// invoke in your code
UIViewController *v= [[MyNewViewController alloc] init];

[self.navigationController popThenPushViewController:v animated:YES];

RELEASE_SAFELY(v);
于 2013-08-20T05:27:15.353 回答
0

对于 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
于 2013-06-25T22:05:49.683 回答
0

不完全是答案,但在某些情况下可能会有所帮助(例如我的):

如果您需要弹出视图控制器 C 并转到 B(堆栈外)而不是 A(C 下方的那个),则可以将 B 推到 C 之前,并将所有 3 个都放在堆栈上。通过保持 B 推送不可见,并选择仅弹出 C 或同时弹出 C 和 B,您可以达到相同的效果。

初始问题 A -> C(我想弹出 C 并显示 B,出栈)

可能的解决方案 A -> B(推到不可见)-> C(当我弹出 C 时,我选择显示 B 或同时弹出它)

于 2015-06-24T17:14:42.617 回答
0

我使用此解决方案来保留动画。

[self.navigationController pushViewController:controller animated:YES];
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newControllers removeObject:newControllers[newControllers.count - 2]];
[self.navigationController setViewControllers:newControllers];
于 2018-02-27T04:28:00.467 回答