如何同时关闭三个模态视图控制器?

3
我有一个应用程序,它有一个初始登录屏幕。当用户想要注册时,他们会看到一个由三个模态视图控制器呈现的注册表单。当用户在第三个屏幕上完成表单(通过按“完成”按钮)时,我希望用户被带回到初始登录屏幕。
我已经尝试在第三个视图控制器中实现这个功能:
[self dismissViewControllerAnimated:NO completion:nil]
[self.presentingViewController dismissViewControllerAnimated:NO completion:nil]
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:NO completion:nil]

然而,它只解散了两个视图控制器,而不是所有3个。这是为什么?

2
为什么你使用这么多的模态框?为什么不使用一个带有控制器的模态框呢? - Wain
我正在使用自定义导航控制器,但我不知道什么是包控制器。 - Vishwa Iyer
笔误,自动更正来自导航控制器。因此,将其推入导航控制器而不是使用多个模态视图。 - Wain
好的,你应该考虑一种重新组织的方法来消除多个模态框。 - Wain
我可以不使用导航控制器来完成吗? - Vishwa Iyer
显示剩余3条评论
3个回答

2
正如其他人所指出的,从用户体验角度来看,有更优雅/高效/易于实现类似结果的方法:通过导航控制器、页面视图控制器或其他容器。
简短/快速的答案是:您需要在呈现视图控制器的链中再进一步,因为解除请求需要发送到呈现控制器,而不是被呈现的控制器。并且您只能向该控制器发送解除请求,它将负责从堆栈中弹出子控制器。
UIViewController *ctrl = self.presentingViewController.presentingViewController.presentingViewController;
[ctrl dismissViewControllerAnimated:NO completion:nil]

为了解释原因,希望能帮助其他人更好地理解iOS中控制器呈现逻辑,下面提供更多细节。
让我们从Apple文档dismissViewControllerAnimated:completion:开始。
引用如下:
关闭由视图控制器模态呈现的视图控制器。 呈现视图控制器负责关闭它所呈现的视图控制器。如果您在所呈现的视图控制器本身上调用此方法,则UIKit会要求呈现视图控制器处理关闭。
因此,[self dismissViewControllerAnimated:NO completion:nil]只是将请求转发给了self.presentingViewController。这意味着前两行代码具有相同的效果(实际上第二行代码没有执行任何操作,因为在第一行代码执行后没有呈现的控制器)。
这就是为什么您关闭视图控制器时只对前两个控制器有效的原因。您应该从self.presentingViewController开始,并沿着呈现视图控制器的链条进行操作。但这不太优雅,如果稍后视图控制器的层次结构发生变化,可能会导致问题。
继续阅读文档,我们发现以下内容:
如果您连续呈现多个视图控制器,从而构建一个呈现视图控制器的堆栈,在堆栈中较低位置的视图控制器上调用此方法将关闭其直接子视图控制器和该子视图控制器上方的所有视图控制器。
因此,您不需要三次调用dismissViewControllerAnimated:completion:方法,对要返回的控制器进行一次调用即可。此时,传递对该控制器的引用比浏览视图控制器堆栈更可靠。
文档中还有一些有用的细节,例如在同时关闭多个控制器时应用哪些转换。
我建议您阅读整个文档,不仅针对此方法,还包括您在应用程序中使用的所有方法/类。您可能会发现一些使生活更轻松的东西。
如果您没有时间阅读Apple关于UIKit的全部文档,您可以在遇到问题时阅读它,就像在这种情况下,dismissViewControllerAnimated:completion:方法不起作用一样。
作为结束语,你的方法还存在一些更微妙的问题,因为实际的解散操作发生在另一个运行循环周期中,可能会生成控制台警告并且表现不如预期。这就是为什么有关呈现/解散其他控制器的进一步操作应该在完成块中完成,以便给 UIKit 完成更新其内部状态的机会。

那么如果我只是在堆栈中解除根视图控制器,那么所有其他视图控制器都会自动解除吗? - Vishwa Iyer
是的,您可以将多个控制器一次性从当前堆栈中弹出。 - Cristik
你的“简短”回答很有效。感谢提供这些信息,它们非常有启发性和趣味性。 - Vishwa Iyer

0
完全明白。我将嵌入一个导航控制器,而不是使用模态方式。我有一个和你类似的情况。我有一个名为LoginViewController的视图控制器作为UINavigationController的根视图控制器。SignupViewController将通过push方法呈现。对于ResetPasswordViewController,我将使用modal,因为它应该返回到LoginViewController,无论结果如何。然后,您可以从SignupViewControllerLoginViewController中解除整个UINavigationController

第二种方法是,您可以自己设计一种机制来引用已呈现的UIViewController,通过共享实例轻松地将其解除。要注意内存管理。在解除后,您应该考虑是否需要立即将其设置为nil。


0

我知道三种方法来关闭多个视图控制器:

  • 使用完成块链

~

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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接