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


84

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

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

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

我得到非常奇怪的行为。通常会出现编辑器视图,但是如果我尝试使用导航栏上的“后退”按钮,则会得到额外的屏幕,其中一些是空白的,有些则被弄乱了。标题也变得随机。就像导航堆栈已完全用软管固定。

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

谢谢,马特

Answers:


137

我发现您根本不需要手动操作该viewControllers属性。基本上有2个棘手的事情。

  1. self.navigationControllernil如果self当前不在导航控制器的堆栈上,则将返回。因此,在无法访问它之前,请将其保存到局部变量。
  2. 您必须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,则新屏幕实际上将进行动画处理,而刚弹出的控制器将进行动画处理。看起来还不错!


辉煌!更好的解决方案
emmby 2010年

太棒了 尽管我不需要调用[[self keep] autorelease],但它仍然可以正常工作。
iamj4de 2010年

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

我在寻找什么。
JERC

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

注意:我将保留/释放更改为保留/自动释放,因为它通常更健壮-如果在保留/释放之间发生异常,您会泄漏自己,但是自动释放会解决这个问题。


7

经过大量的努力(并调整了Kevin的代码),我终于想出了如何在从堆栈中弹出的视图控制器中执行此操作。我遇到的问题是,从控制器数组中删除了最后一个对象后,self.navigationController返回nil。我认为这是由于实例方法navigationController上UIViewController文档中的这一行引起的:“如果视图控制器在其堆栈中,则仅返回导航控制器。”

我认为,一旦将当前视图控制器从堆栈中删除,它的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来说仍然太快了。

约翰


您的代码正是我使用的代码,太好了!谢谢。一个注意事项:使用页面卷曲过渡时,我在视图底部(知道为什么)有一个白色的标记,但使用翻转时效果很好。无论如何,这是不错的紧凑代码!
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

我最近不得不做类似的事情,并将我的解决方案基于迈克尔斯的答案。就我而言,我必须从导航堆栈中删除两个View Controller,然后在其上添加一个新的View Controller。呼唤

[控制器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

对于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

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

在您的ViewController中

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

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

RELEASE_SAFELY(v);

0

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

如果您需要弹出viewcontroller 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];
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.