如何一次关闭 3 个模态视图控制器?

How to dismiss 3 modal view controllers at once?

我有一个应用程序,它有一个初始登录屏幕,然后当用户想要注册时,他们会看到一个以模态方式显示的三个视图控制器的注册表单。当用户在第三个屏幕上完成表单时(通过按 "Done" 按钮),我希望用户被带回初始登录屏幕。

我试过在第三个视图控制器中这样做:

[self dismissViewControllerAnimated:NO completion:nil]
[self.presentingViewController dismissViewControllerAnimated:NO completion:nil]
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:NO completion:nil]

然而,它只解除了两个视图控制器,而不是全部 3 个。为什么会这样?

正如其他人指出的那样,从用户体验的角度来看,有更多 elegant/efficient/easier 方法可以实现类似的结果:通过导航控制器、页面视图控制器或其他容器。

Short/quick 答案:您需要在呈现视图控制器链中更进一步,因为解雇请求需要发送到正在呈现的控制器,而不是正在呈现的控制器。并且您可以仅向该控制器发送解除请求,它将负责从堆栈中弹出子控制器。

UIViewController *ctrl = self.presentingViewController.presentingViewController.presentingViewController;
[ctrl dismissViewControllerAnimated:NO completion:nil]

解释原因,并希望能帮助其他人更好地理解 iOS 中的控制器呈现逻辑,您可以在下面找到更多详细信息。

让我们从 Apple documentation 开始 dismissViewControllerAnimated:completion:

Dismisses the view controller that was presented modally by the view controller.

The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal.

因此[self dismissViewControllerAnimated:NO completion:nil]只是将请求转发给了self.presentingViewController。这意味着前两行具有相同的效果(实际上第二行什么也没做,因为在第一行执行后没有呈现控制器)。

这就是为什么您对视图控制器的解雇只对前 2 个起作用。您应该从 self.presentingViewController 开始,然后沿着呈现视图控制器的链前进。但这不是很优雅,如果以后视图控制器的层次结构发生变化,可能会导致问题。

继续阅读文档,我们偶然发现了这一点:

If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack.

所以你不需要调用dismissViewControllerAnimated:completion:三次,在你想回来的控制器上调用一次就足够了。在这一点上,传递对该控制器的引用比在视图控制器堆栈中导航更可靠。

文档中有一些更有用的详细信息,例如关于一次关闭多个控制器时适用的转换。

我建议您通读整个文档,不仅针对此方法,还针对您在应用程序中使用的所有 methods/classes。您可能会发现一些能让您的生活更轻松的事情。

如果您没有时间阅读 Apple 关于 UIKit 的所有文档,您可以在 运行 遇到问题时阅读它,例如在这种情况下 dismissViewControllerAnimated:completion: 无法正常工作以为会。

作为结束语,您的方法存在一些更微妙的问题,因为实际解雇发生在另一个 运行 循环中,因为它可能会生成控制台警告并且不会按预期运行。这就是为什么应该在完成块中完成关于 presenting/dismissing 其他控制器的进一步操作,以更改 UIKit 以完成更新其内部状态。

我知道三种关闭多个 viewController 的方法:

  • 使用完成块链

~

UIViewController *theVC = self.presentingViewController;
UIViewController *theOtherVC = theVC.presentingViewController;
[self dismissViewControllerAnimated:NO 
                         completion:^
{
    [theVC dismissViewControllerAnimated:NO 
                             completion:^
     {
         [theOtherVC dismissViewControllerAnimated:NO completion:nil];
     }];
}];
  • 使用viewControllers的'viewWillAppear:'方法

~

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    if (self.shouldDismiss)
    {
        CustomVC *theVC = (id)self.presentingViewController;
        theVC.shouldDismiss = YES;
        [self dismissViewControllerAnimated:NO completion:nil];
    }
}
  • 将对 LoginVC1 的引用传递到链的更下方。

(这是迄今为止最好的方法)

假设您有一些 StandardVC,它提供了 LoginVC1。

然后,LoginVC1呈现LoginVC2。

然后,LoginVC2呈现LoginVC3。

一种简单的方法是调用(从 LoginVC3.m 文件中)

[myLoginVC1 dismissViewControllerAnimated:YES completion:nil];

在这种情况下,您的 LoginVC1 将失去其强引用(来自 StandardVC),这意味着 LoginVC2 和 LoginVC3 也将被释放。

因此,您需要做的就是让您的 LoginVC3 知道 LoginVC1 存在。

如果不想传递LoginVC1的引用,可以使用:

[self.presentingViewController.presentingViewController dismissViewControllerAnimated:NO completion:nil]; 

但是,上述方法并不是您想要做的事情的正确方法。

我建议您执行以下操作:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
    if (!self.isUserLoggedIn)
    {
        [UIApplication sharedApplication].keyWindow.rootViewController = self.myLoginVC;
    }
    return YES;
}

然后,当用户完成登录过程后,您可以使用

[UIApplication sharedApplication].keyWindow.rootViewController = self.myUsualStartVC;

完全理解。我要做的是嵌入一个导航控制器而不是使用模态。我有一个情况和你一样。我让 LoginViewController 成为 UINavigationController 的根视图控制器。 SignupViewController将以push方式呈现。对于 ResetPasswordViewController,我将使用 modal,因为无论结果如何,它都应该返回到 LoginViewController。然后,您可以从 SignupViewControllerLoginViewController.

中消除整个 UINavigationController

第二种方法就像是,您想出了自己的机制来通过共享实例引用所呈现的 UIViewController。然后,您可以轻松地将其关闭。小心内存管理。解雇后,你应该考虑是否需要立即将其归零。