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

时间:2009-01-04 04:38:36

标签: iphone cocoa-touch uinavigationcontroller uiviewanimation

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

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

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

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

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

谢谢, 马特

16 个答案:

答案 0 :(得分:137)

我发现你根本不需要手动搞乱viewControllers属性。基本上有两个棘手的事情。

    如果self.navigationController当前不在导航控制器的堆栈上,则
  1. nil将返回self。因此,在失去对它的访问权限之前,请将其保存到本地变量。
  2. 您必须retain(并且release} self或者拥有您所使用方法的对象将被取消分配,从而导致陌生。
  3. 完成准备工作后,只需正常弹出即可。此代码将立即将顶部控制器替换为另一个。

    // 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,那么在最后一行中,新屏幕实际上会生成动画,您刚刚弹出的控制器将会生成动画效果。看起来很漂亮!

答案 1 :(得分: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];

答案 2 :(得分: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];

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

答案 3 :(得分:7)

经过很多努力(并调整了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];

答案 4 :(得分: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来说仍然有点太快..

约翰

答案 5 :(得分: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创建新堆栈。

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:1)

我最近不得不做类似的事情并根据迈克尔斯答案提出解决方案。在我的情况下,我不得不从导航堆栈中删除两个视图控制器,然后添加一个新的视图控制器。两次拨打

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

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 :(得分: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];
}

答案 9 :(得分:1)

NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
    for(int i=0;i<controllers.count;i++){
       [controllers removeLastObject];
    }
 self.navigationController.viewControllers = controllers;

答案 10 :(得分: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];

答案 11 :(得分:0)

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

答案 12 :(得分: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

答案 13 :(得分: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);

答案 14 :(得分:0)

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

如果你需要弹出viewcontroller C并转到B(堆栈外)而不是A(C语言中的那个),可以在C之前推送B,并且堆栈中全部为3。通过保持B推不可见,并选择是否只弹出C或C和B,你可以达到同样的效果。

最初的问题 A - &gt; C(我想弹出C并显示B,没有堆叠)

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

答案 15 :(得分:0)

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

[self.navigationController pushViewController:controller animated:YES];
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newControllers removeObject:newControllers[newControllers.count - 2]];
[self.navigationController setViewControllers:newControllers];