如何从 UINavigationController 弹出视图并在一次操作中将其替换为另一个视图?

Posted

技术标签:

【中文标题】如何从 UINavigationController 弹出视图并在一次操作中将其替换为另一个视图?【英文标题】:How can I pop a view from a UINavigationController and replace it with another in one operation? 【发布时间】:2010-09-29 10:56:50 【问题描述】:

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

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

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

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

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

谢谢, 马特

【问题讨论】:

【参考方案1】:

我发现您根本不需要手动弄乱viewControllers 属性。基本上有两件棘手的事情。

    如果 self 当前不在导航控制器的堆栈中,self.navigationController 将返回 nil。因此,在您无法访问之前将其保存到局部变量中。 你必须retain(并且正确地releaseself,否则拥有你所在方法的对象将被释放,导致奇怪。

一旦你做好了准备,然后像往常一样弹出和推送。此代码将立即将顶部控制器替换为另一个。

// 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,那么新屏幕将实际进入动画,而您刚刚弹出的控制器将动画出来。看起来不错!

【讨论】:

太棒了!更好的解决方案 太棒了。虽然我不需要调用 [[self retain] autorelease],但它仍然可以正常工作。 也许是一个明显的补充,但是你可以把上面的代码放在一个动画块中来动画过渡:[UIView beginAnimations:@"View Flip" context:nil]; [UIView setAnimationDuration:0.80]; [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView:navController.view cache:NO]; [navController pushViewController:newController 动画:YES]; [UIView 提交动画]; 只需删除保留/自动释放行即可与 ARC 完美结合。 @TomerPeled 是的,这个答案已经有将近 5 年的历史了......我认为 ios 3 就是这种情况。API 已经发生了很大的变化,我不确定它是否是最好的答案了。 【参考方案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];

【讨论】:

@LukeRogers,这会给我带来以下警告:在意外状态下完成导航转换。导航栏子视图树可能会损坏。有什么办法抑制它? 使用此解决方案,您将覆盖弹出框。要在 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]; 我在寻找什么。【参考方案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];

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

【讨论】:

【参考方案4】:

经过大量努力(并调整了 Kevin 的代码),我终于弄清楚了如何在从堆栈中弹出的视图控制器中执行此操作。我遇到的问题是 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];

【讨论】:

这给了我一个黑色的整体!【参考方案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】:

我最近不得不做类似的事情,并且我的解决方案基于 Michaels 的回答。在我的情况下,我必须从导航堆栈中删除两个视图控制器,然后添加一个新的视图控制器。调用

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

【讨论】:

【参考方案8】:

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

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

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

【讨论】:

【参考方案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 #导入

@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.navigationControllerpopViewControllerAnimated 之后成为nil

只需弹出和推送,简单易懂,无需访问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);

【讨论】:

【参考方案15】:

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

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

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

可能的解决方案 A -> B(推不可见)-> C(当我弹出 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];

【讨论】:

以上是关于如何从 UINavigationController 弹出视图并在一次操作中将其替换为另一个视图?的主要内容,如果未能解决你的问题,请参考以下文章

带有标签栏的 presentViewController

将 managedObjectContext 发送到 viewController 崩溃

ID:[...] 的 NSManagedObject 已失效

在 UINavigationController 中设置时图像不显示

UINavigationController - 何时释放推送的视图控制器等

使用 UINavigationController 从另一个控制器更新或重新加载 UIViewController?