不知道当前视图控制器如何呈现模态控制器?

22

是否有一种方法可以在不知道可见视图控制器视图的情况下以模态方式呈现视图控制器? 基本上就像在任何时间点显示警报视图一样。

我想要做的事情是:

MyViewController *myVC = [[MyViewController alloc] init];
[myVC showModally];

我希望能够在应用的任何位置调用它,并使其显示在顶部。我不想关心当前的视图控制器是什么。

我计划使用它来显示登录提示。我不想使用警报视图,也不想在整个应用中编写登录演示代码。

你有什么想法吗?或者也许有更好的方法实现这一点?我应该自己实现机制并将一个视图放置在窗口顶部吗?


你使用故事板吗? - SpaceDust__
5个回答

31

好的,您可以跟着这条链。

[UIApplication sharedApplication].delegate.window.rootViewController 开始。

在每个视图控制器上执行以下测试序列。

如果 [viewController isKindOfClass:[UINavigationController class]],则转到 [(UINavigationController *)viewController topViewController]

如果 [viewController isKindOfClass:[UITabBarController class]],则转到 [(UITabBarController *)viewController selectedViewController]

如果 [viewController presentedViewController],则转到 [viewController presentedViewController]


1
我使用了你的想法创建了一个递归方法:https://gist.github.com/MartinMoizard/6537467。非常好用 :) - MartinMoizard
当有一个或多个警报视图打开时,这可能无法正常工作-至少对于旧样式的警报,我发现使用[UIApplication sharedApplication].keyWindow作为更合适的链入点。 - DrMickeyLauer
@DrMickeyLauer 是的,这是一个旧答案。有些事情我会改变。现在,我建议在共享应用程序中迭代var windows,并在每个视图控制器中迭代var childViewControllers - Jeffery Thomas

24

我的Swift解决方案(灵感来自MartinMoizard的gist)

extension UIViewController {
    func presentViewControllerFromVisibleViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
        if let navigationController = self as? UINavigationController {
            navigationController.topViewController?.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)
        } else if let tabBarController = self as? UITabBarController {
            tabBarController.selectedViewController?.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)
        } else if let presentedViewController = presentedViewController {
            presentedViewController.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)
        } else {
            present(viewControllerToPresent, animated: flag, completion: completion)
        }
    }
}

15

这个解决方案提供了顶层视图控制器,让您可以在呈现之前处理任何特殊情况。例如,也许您只想在最顶层的视图控制器不是特定的视图控制器时才呈现您的视图控制器。

extension UIApplication {
    /// The top most view controller
    static var topMostViewController: UIViewController? {
        return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
    }
}

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else {
            return self
        }
    }
}

使用此方法,您可以在不需要了解最上层视图控制器是什么的情况下从任何地方呈现您的视图控制器。

UIApplication.topMostViewController?.present(viewController, animated: true, completion: nil)

如果顶部的视图控制器不是特定的视图控制器,那么才呈现您的视图控制器

if let topVC = UIApplication.topMostViewController, !(topVC is FullScreenAlertVC) {
    topVC.present(viewController, animated: true, completion: nil)
}

需要注意的一点是,如果当前正在显示一个 UIAlertController,UIApplication.topMostViewController 将会返回一个 UIAlertController。在一个 UIAlertController 上面呈现会有奇怪的行为,应该避免这样做。因此,在呈现之前,你应该手动检查 !(UIApplication.topMostViewController is UIAlertController) 或者在 self is UIAlertController 的情况下添加一个 else if 语句来返回 nil。

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else if self is UIAlertController {
            return nil
        } else {
            return self
        }
    }
}

1
非常好的解决方案!省去了我一些挖掘的时间。谢谢! - Paul Lehn

7
你可以在你的应用程序委托中实现以下代码:
AppDelegate.m
-(void)presentViewControllerFromVisibleController:(UIViewController *)toPresent
{
    UIViewController *vc = self.window.rootViewController;
    [vc presentViewController:toPresent animated:YES];
}

AppDelegate.h

-(void)presentViewControllerFromVisibleViewController:(UIViewController *)toPresent;

从何处而来

#import "AppDelegate.h"
...
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
[delegate presentViewControllerFromVisibleViewController:myViewControllerToPresent];

在您的委托中,您正在获取窗口的rootViewController。这将始终可见-它是所有内容的“父”控制器。

7
我认为这通常可以起作用,但并不正确的是根视图控制器总是可见的。屏幕上可能有一个模态视图控制器,如果在这种情况下运行此代码,我不确定会发生什么。 - rdelmar
在这种情况下,您的根视图控制器仍将是呈现模态视图控制器的根视图控制器。 - Undo
4
是的,它仍然是根视图,但如果一个模态视图控制器在屏幕上,并且您尝试从根视图控制器呈现另一个视图控制器,则会收到警告并且该控制器不会被呈现(尝试在未在窗口层次结构中的 ViewController: 上呈现 ...!)。 - rdelmar
完全可以两次调用模态框,只需要在第一个模态框中调用第二个即可。 - rdelmar
1
我最终在窗口的rootViewController上呈现了VC,但我直接从UIApplication视图keyWindow访问了窗口,而不是通过应用程序委托。你为什么要在这里通过应用程序委托呢? - nebs
显示剩余2条评论

2

我认为你并不一定需要知道哪个视图控制器是可见的。你可以获取应用程序的keyWindow,将你的模态视图控制器的视图添加到视图列表的顶部。然后你可以让它像UIAlertView一样工作。

接口文件:MyModalViewController.h

#import <UIKit/UIKit.h>

@interface MyModalViewController : UIViewController
- (void) show;
@end

实现文件:MyModalViewController.m

#import "MyModalViewController.h"


@implementation MyModalViewController

- (void) show {
    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
    //  Configure the frame of your modal's view.
    [window addSubview: self.view];
}

@end

viewDidLoad会被调用吗?因为VC不在VC层次结构中? - Alex Bollbach

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