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

发布于 2024-07-10 08:54:11 字数 544 浏览 6 评论 0原文

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

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

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

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

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

谢谢, 马特

I have an application where I need to remove one view from the stack of a UINavigationController and replace it with another. The situation is that the first view creates an editable item and then replaces itself with an editor for the item. When I do the obvious solution within the first view:

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

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

I get very strange behavior. Usually the editor view will appear, but if I try to use the back button on the nav bar I get extra screens, some blank, and some just screwed up. The title becomes random too. It is like the nav stack is completely hosed.

What would be a better approach to this problem?

Thanks,
Matt

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

筱武穆 2024-07-17 08:54:11

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

  1. 如果 self 当前不在导航控制器的堆栈上,则 self.navigationController 将返回 nil。 因此,在您失去对它的访问权限之前,请将其保存到局部变量中。
  2. 您必须保留(并正确地释放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,那么新屏幕将实际以动画方式显示,而您刚刚弹出的控制器将以动画方式显示。 看起来很不错!

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!

森林散布 2024-07-17 08:54:11

下面的方法对我来说似乎更好,并且也适用于 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];

The following approach seems nicer to me, and also works well with 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];
真心难拥有 2024-07-17 08:54:11

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

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

From experience, you're going to have to fiddle with the UINavigationController's viewControllers property directly. Something like this should work:

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

Note: I changed the retain/release to a retain/autorelease as that's just generally more robust - if an exception occurs between the retain/release you'll leak self, but autorelease takes care of that.

我纯我任性 2024-07-17 08:54:11

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

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];
无言温柔 2024-07-17 08:54:11

谢谢,这正是我所需要的。 我还将其放入动画中以获得页面卷曲:

        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 来说仍然有点太快了..

Johan

Thanks, this was exactly what I needed. I also put this in an animation to get the page curl:

        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 duration is fast, good for 3GS and newer, 0.8 is still a bit too fast for 3G..

Johan

╭⌒浅淡时光〆 2024-07-17 08:54:11

如果您想通过 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 创建新堆栈。

If you want to show any other view controller by popToRootViewController then you need to do following:

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

Now, all your previous stack will be removed and new stack will be created with your required rootViewController.

千纸鹤带着心事 2024-07-17 08:54:11

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

[controllers removeLastObject];

twice, worked fine in my case.

<代码> <代码>

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

>

I had to do a similar thing recently and based my solution on Michaels answer. In my case I had to remove two View Controllers from the Navigation Stack and then add a new View Controller on. Calling

[controllers removeLastObject];

twice, worked fine in my case.

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

在梵高的星空下 2024-07-17 08:54:11

这个 UINavigationController 实例方法可能会起作用...

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

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

This UINavigationController instance method might work...

Pops view controllers until the specified view controller is the top view controller and then updates the display.

- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
萌辣 2024-07-17 08:54:11

这是另一种不需要直接弄乱 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];
}

Here is another approach that doesn't require directly messing with the viewControllers array. Check if the controller has been pop'd yet, if so push it.

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];
}
下壹個目標 2024-07-17 08:54:11
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
    for(int i=0;i<controllers.count;i++){
       [controllers removeLastObject];
    }
 self.navigationController.viewControllers = controllers;
NSMutableArray *controllers = [self.navigationController.viewControllers mutableCopy];
    for(int i=0;i<controllers.count;i++){
       [controllers removeLastObject];
    }
 self.navigationController.viewControllers = controllers;
翻了热茶 2024-07-17 08:54:11

我最喜欢的方法是使用 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];

My favorite way to do it is with a category on UINavigationController. The following should work:

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

Then from your view controller, you can replace the top view with a new by like this:

[self.navigationController replaceTopViewControllerWithViewController: newController];
蓝戈者 2024-07-17 08:54:11

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

You can check with navigation view controllers array which you give you all view controllers that you have added in navigation stack. By using that array you can back navigate to specific view controller.

一绘本一梦想 2024-07-17 08:54:11

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

For monotouch / xamarin IOS:

inside UISplitViewController class;

UINavigationController mainNav = this._navController; 
//List<UIViewController> controllers = mainNav.ViewControllers.ToList();
mainNav.ViewControllers = new UIViewController[] { }; 
mainNav.PushViewController(detail, true);//to have the animation
夏雨凉 2024-07-17 08:54:11

或者,

您可以使用 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

在你的 ViewController 中

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

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

RELEASE_SAFELY(v);

Alternatively,

You can use category to avoid self.navigationController to be nil after popViewControllerAnimated

just pop and push, it's easy to understand, don't need to access 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

In your ViewController

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

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

RELEASE_SAFELY(v);
彼岸花ソ最美的依靠 2024-07-17 08:54:11

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

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

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

可能的解决方案
A-> B(推隐形)-> C(当我弹出C时,我选择显示B或也弹出它)

Not exactly the answer but might be of help in some scenarios (mine for example):

If you need to pop viewcontroller C and go to B (out of stack) instead of A (the one bellow C), it's possible to push B before C, and have all 3 on the stack. By keeping the B push invisible, and by choosing whether to pop only C or C and B altogether, you can achieve the same effect.

initial problem
A -> C (I want to pop C and show B, out of stack)

possible solution
A -> B (pushed invisible) -> C (when I pop C, I choose to show B or also pop it)

極樂鬼 2024-07-17 08:54:11

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

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

I use this solution to keep the animation.

[self.navigationController pushViewController:controller animated:YES];
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[newControllers removeObject:newControllers[newControllers.count - 2]];
[self.navigationController setViewControllers:newControllers];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文