【问题标题】:iPhone - dismiss multiple ViewControllersiPhone - 关闭多个 ViewController
【发布时间】:2011-02-26 00:23:02
【问题描述】:

我的视图控制器层次结构很长;

在第一个 View Controller 中我使用以下代码:

SecondViewController *svc = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
[self presentModalViewController:svc animated:YES];    
[svc release];

在第二个视图控制器中,我使用以下代码:

ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
[self presentModalViewController:tvc animated:YES];    
[tvc release];

等等。

所以有时候我有很多视图控制器,我需要回到第一个视图控制器。 如果我一次返回一步,我会在每个视图控制器中使用这段代码:

[self dismissModalViewControllerAnimated:YES];

如果我想直接从第六个视图控制器返回到第一个视图控制器,我必须做些什么才能一次关闭所有控制器?

谢谢

【问题讨论】:

    标签: iphone uiviewcontroller dismiss


    【解决方案1】:

    关闭顶级 VC 动画,其他的则不关闭。如果你有三个模态VC

    [self dismissModalViewControllerAnimated:NO]; // First
    [self dismissModalViewControllerAnimated:NO]; // Second
    [self dismissModalViewControllerAnimated:YES]; // Third
    

    编辑:如果您只想使用一种方法执行此操作,请将层次结构保存到 VC 数组中并关闭最后一个动画对象,而其他对象则不关闭。

    【讨论】:

    • 如果我在上一个VC中使用你的代码,第二次调用dismissModalViewControllerAnimated会导致崩溃:objc[7035]: FREED(id): message dismissModalViewControllerAnimated: sent to freed object=0x4c8e9a0 程序接收到的信号: “EXC_BAD_INSTRUCTION”。
    • 您必须在每个 VC 中执行此操作,而不是在最后一个中执行此操作,因为在第二行中您没有当前的模态视图控制器。最好的方法是将您的 VC 层次结构保存在一个数组中,并关闭每个不是动画但最后一个的。你可以在你的 AppDelegate 上做到这一点
    • 你必须从第一个模态视图控制器(或者我认为它的父级)中解散才能让它工作。
    • 有时,如果您不使用导航控制器,这是一个非常好的方法 - 您需要让第一个没有动画,否则后续的不会被解雇。不知道为什么这被否决了?
    【解决方案2】:

    我找到了解决办法。

    当然,您可以在最明显的地方找到解决方案,因此请阅读 UIViewController 参考中的 dismissModalViewControllerAnimated 方法...

    如果你呈现几个模态视图 控制器连续,因此 构建一堆模态视图 控制器,在一个 堆栈中较低的视图控制器 关闭其直接子视图 控制器和所有视图控制器 在堆栈上的那个孩子之上。什么时候 发生这种情况,只有最顶层的视图 以动画方式被解雇; 任何中间视图控制器都是 简单地从堆栈中删除。这 最顶层的视图被使用它的 模态过渡风格,可能 不同于其他人使用的样式 查看堆栈中较低的控制器。

    所以在目标视图上调用dismissModalViewControllerAnimated 就足够了。 我使用了以下代码:

    [[[[[self parentViewController] parentViewController] parentViewController] parentViewController] dismissModalViewControllerAnimated:YES];
    

    回到我的家。

    【讨论】:

    • 注意:在 iOS5 中,这改为“presentingViewController”:game4mob.com/index.php/jawbreaker/…
    • 警告:我现在不知道必须弹出多少视图,它不能正常工作。
    • 是的,只要使用这个[self.presentingViewController dismissViewControllerAnimated:NO completion:nil];就可以在任何地方使用
    【解决方案3】:

    首先,Oscar Peli 感谢您的代码。

    要在开始时启动您的 navigationController,您可以通过这种方式使其更具动态性。 (如果您不知道堆栈中 ViewController 的数量)

    NSArray *viewControllers = self.navigationController.viewControllers;
    [self.navigationController popToViewController: [viewControllers objectAtIndex:0] animated: YES];
    

    【讨论】:

      【解决方案4】:

      如果你要回到起点,你可以使用代码 [self.navigationController popToRootViewControllerAnimated:YES];

      【讨论】:

      • 错了。他使用Modal s 进行演示,而不是Pushes。这只有在你有 UINavigationController 时才有效,而在使用 modals 时通常不会这样做。
      • -1: [self.navigationController popToRootViewControllerAnimated:YES] 不会关闭任何呈现的模式视图控制器。
      【解决方案5】:

      试试这个..

      ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
      [self.view addsubview:tvc];    
      [tvc release];
      

      【讨论】:

      • 不应该是[self.view addsubview:tvc.view];
      【解决方案6】:
      [[self presentingViewController]presentingViewController]dismissModalViewControllerAnimated:NO];
      

      你也可以在你想解除的所有控制器中实现一个委托

      【讨论】:

      • dismissModalViewController 已被贬低
      • 您可以创建一个委托并在您想要关闭的所有视图中启用,因此正常的关闭它会出现在视图中
      • Juan,我的问题是我无法关闭导航堆栈上的视图控制器。我已经浏览了几篇关于 SO 的帖子,但没有任何帮助。
      • 我拥有的 VC 的顺序是在按钮 1 上通过 1->2->3->4->5 而对于按钮 2,它通过 1->2->4-> 5.而且我无法驳回 VC 号。 2 以便在 VC 1 号登陆 .... 是否也有任何紧密耦合 bw VC 的父子层次结构?
      【解决方案7】:

      假设您的第一个视图控制器也是根/初始视图控制器(您在情节提要中指定为初始视图控制器的那个)。您可以将其设置为侦听关闭所有呈现的视图控制器的请求:

      在 FirstViewController 中:

      - (void)viewDidLoad {
          [super viewDidLoad];
      
          // listen to any requests to dismiss all stacked view controllers
          [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dismissAllViewControllers:) name:@"YourDismissAllViewControllersIdentifier" object:nil];
      
          // the remainder of viewDidLoad ...
      }
      
      // this method gets called whenever a notification is posted to dismiss all view controllers
      - (void)dismissAllViewControllers:(NSNotification *)notification {
          // dismiss all view controllers in the navigation stack
          [self dismissViewControllerAnimated:YES completion:^{}];
      }
      

      在导航堆栈下方的任何其他视图控制器中,决定我们应该返回导航堆栈的顶部:

      [[NSNotificationCenter defaultCenter] postNotificationName:@"YourDismissAllViewControllersIdentifier" object:self];
      

      这应该会关闭所有带有动画的模态呈现的视图控制器,只留下根视图控制器。如果您的初始视图控制器是 UINavigationController 并且第一个视图控制器被设置为其根视图控制器,这也适用。

      额外提示:通知名称必须相同,这一点很重要。将此通知名称在应用程序中的某个位置定义为变量可能是个好主意,以免因输入错误而导致误传。

      【讨论】:

      • 不错!最直接的解决方案。 Tks
      • 多么聪明的解决方案。
      【解决方案8】:
        id vc = [self presentingViewController];
        id lastVC = self;
        while (vc != nil) {
          id tmp = vc;
          vc = [vc presentingViewController];
          lastVC = tmp;
        }
        [lastVC dismissViewControllerAnimated:YES completion:^{
      }];
      

      【讨论】:

        【解决方案9】:

        如果您使用的是模型视图控制器,您可以使用通知来关闭所有预设的视图控制器。

        1.像这样在RootViewController中注册通知

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(dismissModelViewController)
                                                     name:dismissModelViewController
                                                   object:nil];
        

        2.在rootviewController中实现dismissModelViewController函数

        - (void)dismissModelViewController
        {
            While (![self.navigationController.visibleViewController isMemberOfClass:[RootviewController class]])
            {
                [self.navigationController.visibleViewController dismissViewControllerAnimated:NO completion:nil];
            }
        }
        

        3.Notification 发布每个关闭或关闭按钮事件。

           [[NSNotificationCenter defaultCenter] postNotificationName:dismissModelViewController object:self];
        

        【讨论】:

          【解决方案10】:

          使用这个通用解决方案来解决这个问题:

          - (UIViewController*)topViewController
          {
              UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
              while (topController.presentedViewController) {
                  topController = topController.presentedViewController;
              }
              return topController;
          }
          
          
          - (void)dismissAllModalController{
          
              __block UIViewController *topController = [self topViewController];
          
              while (topController.presentingViewController) {
                  [topController dismissViewControllerAnimated:NO completion:^{
          
                  }];
                  topController = [self topViewController];
              }
          }
          

          【讨论】:

            【解决方案11】:

            这是我用来弹出和关闭所有视图控制器以返回根视图控制器的解决方案。我在 UIViewController 的类别中有这两种方法:

            + (UIViewController*)topmostViewController
            {
                UIViewController* vc = [[[UIApplication sharedApplication] keyWindow] rootViewController];
                while(vc.presentedViewController) {
                    vc = vc.presentedViewController;
                }
                return vc;
            }
            
            + (void)returnToRootViewController
            {
                UIViewController* vc = [UIViewController topmostViewController];
                while (vc) {
                    if([vc isKindOfClass:[UINavigationController class]]) {
                        [(UINavigationController*)vc popToRootViewControllerAnimated:NO];
                    }
                    if(vc.presentingViewController) {
                        [vc dismissViewControllerAnimated:NO completion:^{}];
                    }
                    vc = vc.presentingViewController;
                }
            }
            

            那我就打个电话

            [UIViewController returnToRootViewController];
            

            【讨论】:

              【解决方案12】:

              是的。已经有很多答案了,但无论如何我只想在列表末尾添加一个。问题是我们需要在层次结构的基础上获取对视图控制器的引用。正如@Juan Munhoes Junior 的回答一样,您可以遍历层次结构,但用户可能会采取不同的路线,所以这是一个非常脆弱的答案。扩展这个简单的解决方案并不难,尽管只是简单地遍历层次结构寻找堆栈的底部。在底部调用dismiss也会得到所有其他的。

              -(void)dismissModalStack {
                  UIViewController *vc = self.presentingViewController;
                  while (vc.presentingViewController) {
                      vc = vc.presentingViewController;
                  }
                  [vc dismissViewControllerAnimated:YES completion:NULL];
              }
              

              这既简单又灵活:如果您想在堆栈中查找特定类型的视图控制器,您可以添加基于[vc isKindOfClass:[DesiredViewControllerClass class]] 的逻辑。

              【讨论】:

              • 非常适合我。谢谢。
              【解决方案13】:

              在 Swift 中:

              self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
              

              【讨论】:

                【解决方案14】:

                基于this 评论添加了一些内容的 swift 版本

                func dismissModalStack(viewController: UIViewController, animated: Bool, completionBlock: BasicBlock?) {
                    if viewController.presentingViewController != nil {
                        var vc = viewController.presentingViewController!
                        while (vc.presentingViewController != nil) {
                            vc = vc.presentingViewController!;
                        }
                        vc.dismissViewControllerAnimated(animated, completion: nil)
                
                        if let c = completionBlock {
                            c()
                        }
                    }
                }
                

                【讨论】:

                • 不错!我会明确写出 BasicBlock,因为它没有在您的代码 sn-p 中声明。
                • funcdismissModalStack(animated: Bool, completionBlock: ((Void)->Void)?)
                【解决方案15】:

                简单的递归关闭器:

                extension UIViewController {
                    final public func dismissEntireStackAndSelf(animate: Bool = true) {
                        // Always false on non-calling controller
                        presentedViewController?.ip_dismissEntireStackAndSelf(false)
                        self.dismissViewControllerAnimated(animate, completion: nil)
                    }
                }
                

                这将强制关闭每个子控制器,然后只为自己设置动画。您可以随意切换,但是如果您为每个控制器设置动画,它们会一个接一个地进行,而且速度很慢。

                打电话

                baseController.dismissEntireStackAndSelf()
                

                【讨论】:

                  【解决方案16】:

                  iOS 8+ 通用的全屏关闭方法,没有错误的动画上下文。 在 Objective-C 和 Swift 中

                  目标-C

                  - (void)dismissModalStackAnimated:(bool)animated completion:(void (^)(void))completion {
                      UIView *fullscreenSnapshot = [[UIApplication sharedApplication].delegate.window snapshotViewAfterScreenUpdates:false];
                      [self.presentedViewController.view insertSubview:fullscreenSnapshot atIndex:NSIntegerMax];
                      [self dismissViewControllerAnimated:animated completion:completion];
                  }
                  

                  斯威夫特

                  func dismissModalStack(animated: Bool, completion: (() -> Void)?) {
                      if let fullscreenSnapshot = UIApplication.shared.delegate?.window??.snapshotView(afterScreenUpdates: false) {
                          presentedViewController?.view.addSubview(fullscreenSnapshot)
                      }
                      if !isBeingDismissed {
                          dismiss(animated: animated, completion: completion)
                      }
                  }
                  

                  tl;博士

                  其他解决方案有什么问题?

                  有很多解决方案,但没有一个是错误的解除上下文,所以:

                  例如root A -> Presents B -> Presents C 如果你想从 C 解散到 A,你可以通过在 rootViewController 上调用 dismissViewControllerAnimated 来正式。

                   [[UIApplication sharedApplication].delegate.window.rootViewController dismissModalStackAnimated:true completion:nil];
                  

                  但是从 C 对此根调用驳回将导致正确的行为和错误的转换(会看到 B 到 A 而不是 C 到 A)。


                  所以

                  我创建了通用解除方法。此方法将获取当前全屏快照并将其放在接收者呈现的视图控制器上,然后将其全部关闭。 (示例:从 C 调用默认关闭,但 B 确实被视为关闭)

                  【讨论】:

                  • 为什么不在 NSIntegerMax 使用 addSubview 而不是 insertSubview?
                  • 这种情况下没关系。
                  • 没错,而且 addSubview 更简单更短的方式来实现相同的结果
                  • 这适用于所有模态演示样式,绝对是正确的方法
                  • @JakubTruhlář 添加子视图不起作用是什么情况?
                  【解决方案17】:

                  基于上述答案的 Swift 扩展:

                  extension UIViewController {
                  
                      func dismissUntilAnimated<T: UIViewController>(animated: Bool, viewController: T.Type, completion: ((viewController: T) -> Void)?) {
                          var vc = presentingViewController!
                          while let new = vc.presentingViewController where !(new is T) {
                              vc = new
                          }
                          vc.dismissViewControllerAnimated(animated, completion: {
                              completion?(viewController: vc as! T)
                          })
                      }
                  }
                  

                  Swift 3.0 版本:

                  extension UIViewController {
                  
                      /// Dismiss all modally presented view controllers until a specified view controller is reached. If no view controller is found, this function will do nothing.
                  
                      /// - Parameter reached:      The type of the view controller to dismiss until.
                      /// - Parameter flag:         Pass `true` to animate the transition.
                      /// - Parameter completion:   The block to execute after the view controller is dismissed. This block contains the instance of the `presentingViewController`. You may specify `nil` for this parameter.
                      func dismiss<T: UIViewController>(until reached: T.Type, animated flag: Bool, completion: ((T) -> Void)? = nil) {
                          guard let presenting = presentingViewController as? T else {
                              return presentingViewController?.dismiss(until: reached, animated: flag, completion: completion) ?? ()
                          }
                  
                          presenting.dismiss(animated: flag) {
                              completion?(presenting)
                          }
                      }
                  }
                  

                  完全忘记了我为什么要这样做,因为考虑到大多数时候模态视图控制器的呈现视图控制器是UITabBarController,这完全是无用的,这是非常愚蠢的逻辑。实际获取基本视图控制器实例并在其上调用 dismiss 会更有意义。

                  【讨论】:

                    【解决方案18】:

                    关于dismiss(animated:completion:) 方法的Apple 文档。

                    Discussion部分,它说:

                    any intermediate view controllers are simply removed from the stack.
                    

                    如果您连续呈现多个视图控制器,从而构建一个呈现视图控制器的堆栈,则在堆栈中较低的视图控制器上调用此方法会关闭其直接子视图控制器以及堆栈上该子视图控制器上方的所有视图控制器。发生这种情况时,只有最顶层的视图会以动画方式消失; 任何中间视图控制器都简单地从堆栈中删除。最顶层的视图使用其模态转换样式被解除,这可能与堆栈中其他视图控制器使用的样式不同。

                    换句话说,如果视图控制器堆栈如下所示

                    Root -> A -> B -> C -> D ... -> Z
                    

                    D 调用dismiss 方法,所有隐藏D 的视图控制器,例如:(E ... Z),将从堆栈中移除。

                    【讨论】:

                      【解决方案19】:

                      Swift 3 基于上述答案的扩展。

                      这样的堆栈原理:A -> B -> C -> D

                      • 拍摄 D 的快照
                      • 在 B 上添加此快照
                      • 在没有动画的情况下从 B 中解散
                      • 完成后,通过动画从 A 中解散

                        extension UIViewController {
                        
                            func dismissModalStack(animated: Bool, completion: (() -> Void)?) {
                                let fullscreenSnapshot = UIApplication.shared.delegate?.window??.snapshotView(afterScreenUpdates: false)
                                if !isBeingDismissed {
                                    var rootVc = presentingViewController
                                    while rootVc?.presentingViewController != nil {
                                        rootVc = rootVc?.presentingViewController
                                    }
                                    let secondToLastVc = rootVc?.presentedViewController
                                    if fullscreenSnapshot != nil {
                                        secondToLastVc?.view.addSubview(fullscreenSnapshot!)
                                    }
                                    secondToLastVc?.dismiss(animated: false, completion: {
                                        rootVc?.dismiss(animated: true, completion: completion)
                                    })
                                }
                            }
                        }
                        

                      在模拟器上有点闪烁,但在设备上没有。

                      【讨论】:

                      • 当只有一个视图控制器时,动画是不可见的。感觉像是一个小故障。
                      【解决方案20】:

                      对于 Swift 3.0+

                      self.view.window!.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
                      

                      这将关闭你的所有呈现的视图控制器 根视图控制器。

                      【讨论】:

                        【解决方案21】:

                        大多数解决方案的问题在于,当您关闭已呈现的 viewController 堆栈时,用户将在堆栈中短暂看到第一个呈现的 viewController,因为它正在被关闭。 Jakub 的出色解决方案解决了这个问题。这是基于他的回答的扩展。

                        extension UIViewController {
                        
                            func dismissAll(animated: Bool, completion: (() -> Void)? = nil) {
                                if let optionalWindow = UIApplication.shared.delegate?.window, let window = optionalWindow, let rootViewController = window.rootViewController, let presentedViewController = rootViewController.presentedViewController  {
                                    if let snapshotView = window.snapshotView(afterScreenUpdates: false) {
                                        presentedViewController.view.addSubview(snapshotView)
                                        presentedViewController.modalTransitionStyle = .coverVertical
                                    }
                                    if !isBeingDismissed {
                                        rootViewController.dismiss(animated: animated, completion: completion)
                                    }
                                }
                            }
                        
                        }
                        

                        用法:从任何你想要解散的视图控制器调用这个扩展函数。

                        @IBAction func close() {
                            dismissAll(animated: true)
                        }
                        

                        【讨论】:

                        • 对我来说效果很好。可以使用守卫重写代码以使其更清晰,但它可以按原样工作!
                        • 谢谢你,哈里斯这真的很有帮助:)
                        【解决方案22】:

                        swift 4Xcode 9 这会对你有所帮助。

                        var vc : UIViewController = self.presentingViewController!
                                while ((vc.presentingViewController) != nil) {
                                    vc = vc.presentingViewController!
                                }
                                vc.dismiss(animated: true, completion: nil)
                        

                        享受!!! :)

                        【讨论】:

                          猜你喜欢
                          • 2012-03-23
                          • 1970-01-01
                          • 1970-01-01
                          • 2012-04-30
                          • 2017-12-19
                          • 1970-01-01
                          • 2023-03-10
                          • 1970-01-01
                          相关资源
                          最近更新 更多