场景:用户点击视图控制器上的按钮。视图控制器是导航堆栈中最顶层的(很明显)。tap调用在另一个类上调用的实用程序类方法。这里发生了不好的事情我想在控件返回到视图控制器之前在那里显示一个警告。

+ (void)myUtilityMethod {
    // do stuff
    // something bad happened, display an alert.
}

这是可能的UIAlertView(但可能不太合适)。

在这种情况下,你如何在myUtilityMethod中呈现UIAlertController ?


当前回答

在Objective-C中显示警报的简单方法:

[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alertController animated:YES completion:nil];

alertController是你的UIAlertController对象。

注意:你还需要确保你的助手类扩展了UIViewController

其他回答

几个月前我发过一个类似的问题,我想我终于解决了这个问题。如果你只是想看代码,请点击我文章底部的链接。

解决方案是使用一个额外的UIWindow。

当你想要显示你的UIAlertController:

使你的窗口成为键和可见窗口(window. makekeyandvisible ()) 只需使用一个普通的UIViewController实例作为新窗口的rootViewController。(窗口。rootViewController = UIViewController()) 在你窗口的rootViewController上显示你的UIAlertController

有几点需要注意:

你的UIWindow必须是强引用的。如果它没有被强引用,它将永远不会出现(因为它已经被释放了)。我建议使用属性,但我也成功地使用了关联对象。 为了确保窗口显示在其他所有内容之上(包括系统UIAlertControllers),我设置了windowLevel。(窗口。windowLevel = UIWindowLevelAlert + 1)

最后,我有一个完整的实现,如果你只是想看看。

https://github.com/dbettermann/DBAlertController

斯威夫特5

我刚刚创建了一个新窗口,并在其中添加了警报视图控制器。

查看类TopViewController:

https://gist.github.com/odnaks/3f3fd0d20f318c6276e76d0f9d7de5a7

我使用它很简单,像UIAlertController:

 let alert = TopAlertController()
 alert.title = "title"
 alert.message = "message"
 alert.addAction(UIAlertAction(title: "Ок", style: .default, handler: { _ in }))
 alert.show()

补充Zev的回答(并切换回Objective-C),你可能会遇到这样的情况,你的根视图控制器通过segue或其他东西呈现其他VC。在根VC上调用presenttedviewcontroller会处理这个:

[[UIApplication sharedApplication].keyWindow.rootViewController.presentedViewController presentViewController:alertController animated:YES completion:^{}];

这解决了一个问题,我有根VC已经segue到另一个VC,而不是显示警报控制器,像上面报告的警告发出:

Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!

我还没有测试它,但如果你的根VC恰好是一个导航控制器,这可能也是必要的。

对于iOS 13,基于mythicalcoder和bobbyrehm的回答:

在iOS 13中,如果你正在创建自己的窗口来显示警报,你需要保持对该窗口的强引用,否则你的警报将不会显示,因为当它的引用退出作用域时,窗口将立即被释放。

此外,在警报解除后,您需要再次将引用设置为nil,以便删除窗口,继续允许用户在它下面的主窗口上进行交互。

你可以创建一个UIViewController子类来封装窗口内存管理逻辑:

class WindowAlertPresentationController: UIViewController {

    // MARK: - Properties

    private lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
    private let alert: UIAlertController

    // MARK: - Initialization

    init(alert: UIAlertController) {

        self.alert = alert
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {

        fatalError("This initializer is not supported")
    }

    // MARK: - Presentation

    func present(animated: Bool, completion: (() -> Void)?) {

        window?.rootViewController = self
        window?.windowLevel = UIWindow.Level.alert + 1
        window?.makeKeyAndVisible()
        present(alert, animated: animated, completion: completion)
    }

    // MARK: - Overrides

    override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {

        super.dismiss(animated: flag) {
            self.window = nil
            completion?()
        }
    }
}

你可以这样使用它,或者如果你想在你的UIAlertController上使用一个方便的方法,你可以把它扔到一个扩展中:

extension UIAlertController {

    func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) {

        let windowAlertPresentationController = WindowAlertPresentationController(alert: self)
        windowAlertPresentationController.present(animated: animated, completion: completion)
    }
}

斯威夫特 4+

解决方案我使用多年没有任何问题。首先,我扩展UIWindow来找到它的visibleViewController。注意:如果您使用自定义集合*类(如侧菜单),您应该在以下扩展中为这种情况添加处理程序。在获得top most视图控制器后,很容易呈现UIAlertController,就像UIAlertView一样。

extension UIAlertController {

  func show(animated: Bool = true, completion: (() -> Void)? = nil) {
    if let visibleViewController = UIApplication.shared.keyWindow?.visibleViewController {
      visibleViewController.present(self, animated: animated, completion: completion)
    }
  }

}

extension UIWindow {

  var visibleViewController: UIViewController? {
    guard let rootViewController = rootViewController else {
      return nil
    }
    return visibleViewController(for: rootViewController)
  }

  private func visibleViewController(for controller: UIViewController) -> UIViewController {
    var nextOnStackViewController: UIViewController? = nil
    if let presented = controller.presentedViewController {
      nextOnStackViewController = presented
    } else if let navigationController = controller as? UINavigationController,
      let visible = navigationController.visibleViewController {
      nextOnStackViewController = visible
    } else if let tabBarController = controller as? UITabBarController,
      let visible = (tabBarController.selectedViewController ??
        tabBarController.presentedViewController) {
      nextOnStackViewController = visible
    }

    if let nextOnStackViewController = nextOnStackViewController {
      return visibleViewController(for: nextOnStackViewController)
    } else {
      return controller
    }
  }

}