47

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

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

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

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

谢谢

4

22 回答 22

67

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

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

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

于 2015-01-06T22:06:52.383 回答
26

我找到了解决方案。

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

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

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

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

回到我的家。

于 2010-06-02T12:49:55.470 回答
21

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

Objective-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,你可以通过调用dismissViewControllerAnimated.rootViewController

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

然而,从 C 对这个根调用解除将导致正确的行为和错误的转换(会看到 B 到 A 而不是 C 到 A)。


所以

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

于 2016-06-02T21:11:52.243 回答
15

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

在 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 并且第一个视图控制器设置为其根视图控制器,这也适用。

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

于 2013-08-31T10:41:42.857 回答
6
[[self presentingViewController]presentingViewController]dismissModalViewControllerAnimated:NO];

您还可以在要关闭的所有控制器中实现委托

于 2013-03-01T17:11:24.633 回答
6

大多数解决方案的问题在于,当您关闭已呈现的 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)
}
于 2017-12-23T19:03:01.203 回答
4

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

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];
于 2014-02-28T09:25:32.840 回答
4

在斯威夫特:

self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
于 2015-03-26T17:25:24.303 回答
3

试试这个..

ThirdViewController *tvc = [[ThirdViewController alloc] initWithNibName:@"ThirdViewController" bundle:nil];
[self.view addsubview:tvc];    
[tvc release];
于 2012-11-27T09:05:21.010 回答
2

基于上述答案的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)
                })
            }
        }
    }
    

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

于 2017-03-01T21:36:05.210 回答
1

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

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

NSArray *viewControllers = self.navigationController.viewControllers;
[self.navigationController popToViewController: [viewControllers objectAtIndex:0] animated: YES];
于 2011-02-23T14:35:48.620 回答
1
  id vc = [self presentingViewController];
  id lastVC = self;
  while (vc != nil) {
    id tmp = vc;
    vc = [vc presentingViewController];
    lastVC = tmp;
  }
  [lastVC dismissViewControllerAnimated:YES completion:^{
}];
于 2013-09-26T14:45:25.597 回答
1

使用此通用解决方案来解决此问题:

- (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];
    }
}
于 2014-06-17T16:32:57.297 回答
1

这是我用来弹出和关闭所有视图控制器以返回根视图控制器的解决方案。我在 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];
于 2014-12-14T11:37:46.537 回答
1

一个快速版本,基于评论添加了一些内容

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()
        }
    }
}
于 2015-11-02T17:22:45.773 回答
1

简单的递归关闭器:

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()
于 2016-01-28T00:06:09.397 回答
1

基于上述答案的 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)
        })
    }
}

斯威夫特 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

于 2016-08-05T12:40:34.253 回答
1

对于 Swift 3.0+

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

这将关闭您的 rootviewcontroller 上所有呈现的视图控制器。

于 2017-06-26T12:15:56.073 回答
0

解雇顶级 VC 动画而其他的则不。如果你有三个模态VC

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

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

于 2010-05-31T14:53:45.900 回答
0

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

在第 部分Discussion中,它说:

any intermediate view controllers are simply removed from the stack.

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

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

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

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

于 2017-02-17T05:43:00.220 回答
0

swift 4Xcode 9中,这将对您有所帮助。

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

享受 !!!:)

于 2018-01-12T11:32:54.343 回答
-1

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

于 2011-10-07T14:41:51.193 回答