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

发布于 2024-09-09 06:20:24 字数 354 浏览 7 评论 0原文

因此,有一个包含三个视图控制器的堆栈,其中 A 是根,B 是第一个模态视图控制器,C 是第三个模态 vc。我想立即从 C 转到 A。我已经尝试过 这个解决方案解雇。它确实可以工作,但方式不正确。也就是说,当最后一个视图控制器被关闭时,它将在第一个视图控制器显示之前短暂显示第二个视图控制器。我正在寻找的是一种从第三个 vc 到一个漂亮动画中的第一个 vc 的方法,而不会注意到第二个视图。对此的任何帮助都非常感激。

So have a stack with three view controllers where A is root, B is first modal view controller and C is third modal vc. I would like to go from C to A at once. I have tried this solution to dismiss.It does work but not in a correct way. That is when the last view controller is dismissed it will breifly show the second view controller before the first is shown. What I'm looking for is a way to get from the third vc to the first in one nice animation without noticing the second view. Any help on this is greatly appriciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

很酷不放纵 2024-09-16 06:20:24

确保您只调用 dismissModalViewControllerAnimated: 一次。

我发现要求关闭每个堆叠的模态视图控制器将导致它们都产生动画。

您有: A =modal>; B=模态> C

你应该只调用 [myViewControllerA解雇ModalViewControllerAnimated:YES]

如果你使用[myViewControllerB解雇ModalViewControllerAnimated:YES],它会解雇C,而不是B。在正常情况下(unstacked)使用,它会解雇B(由于响应者链将消息冒泡到A)。在您描述的堆叠场景中,B 是父视图控制器,这优先于模态视图控制器。

Be sure that you're only calling dismissModalViewControllerAnimated: once.

I have found that asking to dismiss each stacked modal view controller will cause both of them to animate.

You have: A =modal> B =modal> C

You should only call [myViewControllerA dismissModalViewControllerAnimated:YES]

If you use [myViewControllerB dismissModalViewControllerAnimated:YES], it will dismiss C, and not B. In normal (unstacked) use, it would dismiss B (due to the responder chain bubbling the message up to A). In the stacked scenario that you describe B is a parent view controller and this takes precedence over being a modal view controller.

请止步禁区 2024-09-16 06:20:24

尽管接受的答案确实对我有用,但它现在可能已经过时了,并且留下了一个看起来很奇怪的动画,其中最上面的模态将立即消失,并且动画将位于后面的模态视图上。我尝试了很多方法来避免这种情况,最终不得不使用一些技巧来让它看起来不错。
注意:(仅在 iOS8+ 中测试,但应该适用于 iOS7+)

基本上,viewControllerA 创建一个以 viewControllerB 作为 rootview 的 UINavigationController 并以模态方式呈现它。

// 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,但是在呈现它之后,我们将把 viewControllerC 的快照放在上面viewControllerB 导航控制器上的视图层。然后,当 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;
}

现在我们只需要确保我们将missBlock定义为ViewControllerC.h 中的一个属性(显然,您可以用委托方法或其他同样令人兴奋的设计模式替换整个部分,重要的部分是在 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];
}

希望这有帮助,快乐的编程:)

Although the accepted answer did work for me, it may be outdated now and left a weird looking animation where the topmost modal would immediately disappear and the animation would be on the rear modalview. I tried many things to avoid this and ended up having to use a bit of a hack to have it look nice.
Note:(only tested in iOS8+, but should work iOS7+)

Basically, viewControllerA creates a UINavigationController with viewControllerB as the rootview and presents it modally.

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

Now in viewControllerB we are going to present viewControllerC the same way, but after we present it, we are going to put a snapshot of viewControllerC over the view layer on viewControllerB's navigation controller. Then, when viewControllerC disappears during dismissal, we won't see the change and the animation will look beautiful.

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

Below are my helper functions that are used to present the view and handle dismissal.
One thing to note, I am using Purelayout for adding auto layout constraints. You can modify this to add them manually or get Purelayout at
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;
}

Now we just need to ensure we have the dismissBlock defined as a property in ViewControllerC.h (you can obviously replace this whole part with delegate methods or other equally as exciting design patterns, the important part is to handle dismissal at the viewControllerB level)

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

Hope this helps, happy programming :)

恋你朝朝暮暮 2024-09-16 06:20:24

对于任何正在寻找解决方法的人,您可以这样做:

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

For anyone looking for a work around you can do this:

  1. Cover everything with a snapshot of the window.
  2. Dismiss both view controllers without animation.
  3. Present a copy of the snapshot in another view controller without animation.
  4. Remove the snapshot covering the window.
  5. Dismiss the snapshot view controller with animation.

Here's the code:

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)
    }
}
〗斷ホ乔殘χμё〖 2024-09-16 06:20:24

您可以通过以下简单方法“回家”:

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

Here's a simple way in you can "dismiss to home":

    var vc: UIViewController = self
    while vc.presentingViewController != nil {
        vc = vc.presentingViewController!
    }
    vc.dismiss(animated: true, completion: nil)
画尸师 2024-09-16 06:20:24

您可以在 rootViewController 中关闭这些 modalViewController。

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

You may dismiss these modalViewControllers at your rootViewController.

    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];
    }
花之痕靓丽 2024-09-16 06:20:24

您可以递归地找到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)
    }
    
}

You can recursively find presentingViewController to get to the root:

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)
    }
    
}
彡翼 2024-09-16 06:20:24

你想用的是
popToRootViewControllerAnimated:。它可以让您到达根控制器,而不显示所有中间控制器。

What you want to use is
popToRootViewControllerAnimated:. It gets you to the root controller without showing all the intervening ones.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文