iPhone - 关闭多个ViewControllers

时间:2010-05-31 14:37:24

标签: iphone uiviewcontroller dismiss

我有一个很长的View Controllers层次结构;

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

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

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

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

等等。

所以有一段时间我有很多View Controller,我需要回到第一个View Controller。 如果我一次回来一步,我会在每个View Controller中使用这段代码:

[self dismissModalViewControllerAnimated:YES];

如果我想直接从第六个View Controller返回到第一个,我必须做什么才能立即解雇所有控制器?

由于

22 个答案:

答案 0 :(得分:62)

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

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

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

答案 1 :(得分:24)

我找到了解决方案。

当然你可以在最明显的地方找到解决方案,所以从UIViewController参考中读取dismissModalViewControllerAnimated方法......

  

如果你提出几种模态视图   控制器连续,因此   建立一堆模态视图   控制器,调用此方法   查看控制器在堆栈中较低   驳回其直接的儿童观点   控制器和所有视图控制器   在堆栈上面的那个孩子。什么时候   发生这种情况,只有最顶层的视图   以动画的方式被驳回;   任何中间视图控制器   只是从堆栈中删除。该   最顶层的视图使用它被驳回   模态过渡风格,可能   与其他人使用的风格不同   查看控制器在堆栈中较低。

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

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

回到我家。

答案 2 :(得分:15)

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;博士

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

有许多解决方案,但没有一个解决错误的解雇背景,所以:

e.g。 根A - >礼物B - >提交C ,如果你想从C解雇A,你可以在dismissViewControllerAnimated上致电rootViewController来正式解雇。

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

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

所以

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

答案 3 :(得分:14)

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

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

额外提示:通知名称相同非常重要。将应用程序中某个通知名称定义为变量可能是一个好主意,因为不会因输入错误而导致错误通知。

答案 4 :(得分:5)

[[self presentingViewController]presentingViewController]dismissModalViewControllerAnimated:NO];

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

答案 5 :(得分:3)

如果您使用的是模型视图控制器,则可以使用通知来解除所有预先设置的视图控制器。

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.每次关闭或解除按钮事件后发布通知。

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

答案 6 :(得分:3)

在斯威夫特:

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

答案 7 :(得分:2)

试试这个..

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

答案 8 :(得分:2)

大多数解决方案的问题在于,当您关闭呈现的viewControllers堆栈时,用户将在解除堆栈时简要地看到堆栈中第一个呈现的viewController。 Jakub的出色解决方案解决了这个问题。这是基于他答案的扩展。

ss.getRange("xxxx").setFormulas(formulasToApply); 
ss.getRange("xxxx").copyTo("YYYY");

// apply all changes right now
SpreadsheetApp.flush();

// now can copy calculated formulas results
ss.getRange("YYYY").copyTo("YYYY", {contentsOnly:true});

用法:从你想要解除回根的任何呈现的viewController中调用此扩展函数。

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)
            }
        }
    }

}

答案 9 :(得分: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];

答案 10 :(得分:1)

Swift 3 扩展基于以上答案。

类似堆栈的原则:A - &gt; B - &gt; C - &gt; 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)
                })
            }
        }
    }
    

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

答案 11 :(得分:1)

适用于Swift 3.0 +

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

这将解除您所有提供的视图控制器   RootViewController的。

答案 12 :(得分: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)
        })
    }
}

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更有意义。

答案 13 :(得分: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()

答案 14 :(得分:1)

基于this评论

的一些新增版本
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()
        }
    }
}

答案 15 :(得分: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];
    }
}

答案 16 :(得分:1)

  id vc = [self presentingViewController];
  id lastVC = self;
  while (vc != nil) {
    id tmp = vc;
    vc = [vc presentingViewController];
    lastVC = tmp;
  }
  [lastVC dismissViewControllerAnimated:YES completion:^{
}];

答案 17 :(得分:1)

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

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

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

答案 18 :(得分:0)

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

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

享受!!! :)

答案 19 :(得分:0)

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

Discussion部分,它说:

any intermediate view controllers are simply removed from the stack.
  

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

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

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

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

答案 20 :(得分:0)

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

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

编辑:如果你只想用一个方法做这个,把你的层次结构保存到一个VC数组中,并解除最后一个动画对象,而不是其他对象。

答案 21 :(得分:-1)

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