一次性关闭多个模态视图控制器?

27

所以有一个堆栈,其中包含三个视图控制器,其中A是根视图控制器,B是第一个模态视图控制器,C是第三个模态视图控制器。我想一次从C到A。我尝试使用此解决方案进行dismiss。它可以起作用,但不是正确的方式。也就是说,当最后一个视图控制器被解除显示时,它会短暂地显示第二个视图控制器,然后再显示第一个视图控制器。我正在寻找的是一种方法,在不注意第二个视图的情况下以一个漂亮的动画从第三个视图控制器到达第一个视图控制器。非常感谢任何对此的帮助。


更通用的方法来关闭多个模态视图控制器在这里。链接 - Ramis
7个回答

28

请确保只调用一次dismissModalViewControllerAnimated:

我发现请求依次关闭每个模态视图控制器会导致两个视图控制器同时执行动画。

假设你有: A =modal> B =modal> C

你应该只调用[myViewControllerA dismissModalViewControllerAnimated:YES]

如果你使用[myViewControllerB dismissModalViewControllerAnimated:YES],它将关闭C而不是B。在正常(非嵌套)情况下,它将关闭B(由于响应者链将消息传递到A)。在你描述的嵌套场景中,B是父视图控制器,这比作为模态视图控制器更优先。


7
目前我使用的代码是:[[[self parentViewController] parentViewController] dismissModalViewControllerAnimated:YES]; 这会使用根视图控制器来dismiss模态视图。但是,仍然会短暂地显示第二个视图控制器。 - sebrock
哦,我应该说我使用的是Utility模板,第二个vc是翻转视图。第三个是从翻转视图实例化的模态vc。 - sebrock
1
注意:在iOS5中,这已更改为“presentingViewController”http://game4mob.com/index.php/jawbreaker/66-dismiss-modal-view-in-ios5-sdk - Tom Kidd

4

对于想要解决此问题的人,你可以这样做:

  1. 使用窗口快照覆盖所有内容。
  2. 不使用动画关闭两个视图控制器。
  3. 在另一个视图控制器中不使用动画呈现快照的拷贝。
  4. 移除遮盖窗口的快照。
  5. 使用动画关闭快照视图控制器。

以下是代码:

let window = UIApplication.shared.keyWindow!
let snapshot = window.snapshotView(afterScreenUpdates: false)!
window.addSubview(snapshot)

let baseViewController = self.presentingViewController!.presentingViewController!

baseViewController.dismiss(animated: false) {
    let snapshotCopy = snapshot.snapshotView(afterScreenUpdates: false)!
    let snapshotViewController = UIViewController()
    snapshotViewController.view.addSubview(snapshotCopy)

    baseViewController.present(snapshotViewController, animated: false) {
        snapshot.removeFromSuperview()
        baseViewController.dismiss(animated: true, completion: nil)
    }
}

4
尽管接受的答案对我有效,但它现在可能已经过时,并留下了一个奇怪的动画,顶部模态会立即消失并且动画会出现在后面的 modalview 上。 我尝试了很多方法来避免这种情况,最终不得不使用一些技巧使其好看。 注:(仅在 iOS8+ 中测试过,但应该适用于 iOS7+)
基本上,viewControllerA 创建了一个 UINavigationController,并将 viewControllerB 作为 rootview 呈现为模态视图。
// ViewControllerA.m
- (void)presentViewB {
    ViewControllerB *viewControllerB = [[ViewControllerB alloc] init];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewControllerB];

    navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
    [self presentViewController:navigationController animated:YES completion:nil];
}

现在在viewControllerB中,我们要以同样的方式展示viewControllerC,但是在我们展示它之后,我们将在viewControllerB的导航控制器上的视图层上放置一个viewControllerC的快照。然后,当viewControllerC在解除显示时消失时,我们将看不到变化,动画将看起来很美观。
//ViewControllerB.m
- (void)presentViewC {
    ViewControllerC *viewControllerC = [[ViewControllerC alloc] init];

    // Custom presenter method to handle setting up dismiss and snapshotting 
    // I use this in a menu that can present many VC's so I centralized this part.
    [self presentViewControllerForModalDismissal:viewControllerC];
}

以下是我用来呈现视图和处理消失的辅助函数。 需要注意的一点是,我使用Purelayout添加自动布局约束。您可以修改此内容以手动添加它们或获取Purelayout, https://github.com/PureLayout/PureLayout
#pragma mark - Modal Presentation Helper functions
- (void)presentViewControllerForModalDismissal:(UIViewController*)viewControllerToPresent {
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewControllerToPresent];
    navigationController.modalPresentationStyle = UIModalPresentationFormSheet;

    // Ensure that anything we are trying to present with this method has a dismissBlock since I don't want to force everything to inherit from some base class. 
    NSAssert([viewControllerToPresent respondsToSelector:NSSelectorFromString(@"dismissBlock")], @"ViewControllers presented through this function must have a dismissBlock property of type (void)(^)()");
    [viewControllerToPresent setValue:[self getDismissalBlock] forKey:@"dismissBlock"];

    [self presentViewController:navigationController animated:YES completion:^{
        // We want the presented view and this modal menu to dismiss simultaneous. The animation looks weird and immediately becomes the menu again when dismissing.
        // So we are snapshotting the presented view and adding it as a subview so you won't see the menu again when dismissing.
        UIView *snapshot = [navigationController.view snapshotViewAfterScreenUpdates:NO];
        [self.navigationController.view addSubview:snapshot];
        [snapshot autoPinEdgesToSuperviewEdges];
    }];
}

- (void(^)()) getDismissalBlock {
    __weak __typeof(self) weakSelf = self;
    void(^dismissBlock)() = ^{
        __typeof(self) blockSafeSelf = weakSelf;
        [blockSafeSelf.navigationController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    };

    return dismissBlock;
}

现在我们只需要确保在ViewControllerC.h中将dismissBlock定义为属性(你可以用代理方法或其他同样激动人心的设计模式来替换整个部分,重要的是在viewControllerB级别处理消除)

// ViewControllerC.h
@interface ViewControllerC : UIViewController
@property (nonatomic, copy) void (^dismissBlock)(void);
@end

//ViewControllerC.m
// Make an method to handle dismissal that is called by button press or whatever logic makes sense.
- (void)closeButtonPressed {
    if (_dismissBlock)  {// If the dismissblock property was set, let the block handle dismissing
        _dismissBlock();
        return;
    }

    // Leaving this here simply allows the viewController to be presented modally as the base as well or allow the presenter to handle it with a block.
    [self dismissViewControllerAnimated:YES completion:nil];
}

希望这能帮到你,愉快地编程 :)

点赞一个可行的解决方案,但这太疯狂了(与其效益相比)。 - Marcel Zanoni
1
虽然有一些解决方案,但实际上并没有一个既易用又美观的好方法。我对苹果没有提供一个好的处理方式感到失望。希望今年秋天的新发布版本能够解决这个问题。 - johnrechd
绝对正确。我选择了另一种方式,依次解除了viewControllers(这不是问题的解决方案,但它可以工作,而且并不那么丑陋)。 - Marcel Zanoni

3

以下是一种简单的方式,您可以使用它“回到主页”:

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

0

你可以在你的rootViewController中dismiss这些modalViewControllers。

    UIViewController *viewController = yourRootViewController;

    NSMutableArray *array = [NSMutableArray array];
    while (viewController.modalViewController) {
        [array addObject:viewController];
        viewController = viewController.modalViewController;
    }

    for (int i = 0; i < array.count; i++) {
        UIViewController *viewController = array[array.count-1-i];
        [viewController dismissModalViewControllerAnimated:NO];
    }

0

您可以递归查找presentingViewController以到达根视图:

extension UIViewController {
    
    private func _rootPresentingViewController(_ vc:UIViewController, depth:Int) -> UIViewController? {
        guard let parentPresenter = vc.presentingViewController else {
            return vc
        }
        if depth > 20 {
            return nil
        }
        return _rootPresentingViewController(parentPresenter, depth: depth + 1)
    }
    
    @objc
    func rootPresentingViewController() -> UIViewController? {
        return _rootPresentingViewController(self, depth: 0)
    }
    
}

-6
你想要使用的是popToRootViewControllerAnimated:。它可以让你回到根控制器,而不显示所有中间的控制器。

1
如果A是UINavigationController的根视图控制器,那么这是一个很好的解决方案。 - ohhorob
1
哦,是的。我刚醒来,所以没听清楚。 - lucius

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