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

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

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

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


当前回答

你可以尝试在UIViewController上用一个方法来实现一个类别 - (void)presentErrorMessage;,在这个方法中你实现了UIAlertController,然后在self上呈现它。然后在你的客户端代码中,你会有这样的东西:

[myViewController presentErrorMessage];

这样可以避免不必要的参数和关于视图不在窗口层次结构中的警告。

其他回答

你可以用Swift 2.2做以下事情:

let alertController: UIAlertController = ...
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

Swift 3.0:

let alertController: UIAlertController = ...
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)

泽夫·艾森伯格的答案简单而直接,但它并不总是有效,它可能会因为以下警告信息而失败:

Warning: Attempt to present <UIAlertController: 0x7fe6fd951e10>  
 on <ThisViewController: 0x7fe6fb409480> which is already presenting 
 <AnotherViewController: 0x7fe6fd109c00>

这是因为windows rootViewController不在所呈现视图的顶部。为了纠正这个问题,我们需要沿着表示链向上走,正如我用Swift 3编写的UIAlertController扩展代码所示:

   /// show the alert in a view controller if specified; otherwise show from window's root pree
func show(inViewController: UIViewController?) {
    if let vc = inViewController {
        vc.present(self, animated: true, completion: nil)
    } else {
        // find the root, then walk up the chain
        var viewController = UIApplication.shared.keyWindow?.rootViewController
        var presentedVC = viewController?.presentedViewController
        while presentedVC != nil {
            viewController = presentedVC
            presentedVC = viewController?.presentedViewController
        }
        // now we present
        viewController?.present(self, animated: true, completion: nil)
    }
}

func show() {
    show(inViewController: nil)
}

2017年9月15日更新:

经过测试并确认,上述逻辑在新推出的iOS 11转基因种子中仍然有效。然而,敏捷视觉投票最多的方法并没有:在新创建的UIWindow中显示的警报视图位于键盘下方,可能会阻止用户点击按钮。这是因为在iOS 11中,所有高于键盘窗口的窗口级别都会降低到低于键盘窗口的级别。

从keyWindow中呈现的一个工件是,当警报出现时,键盘向下滑动,当警报被取消时,键盘再次向上滑动。如果你想让键盘在显示过程中保持在那里,你可以尝试从顶部窗口本身显示,如下所示代码:

func show(inViewController: UIViewController?) {
    if let vc = inViewController {
        vc.present(self, animated: true, completion: nil)
    } else {
        // get a "solid" window with the highest level
        let alertWindow = UIApplication.shared.windows.filter { $0.tintColor != nil || $0.className() == "UIRemoteKeyboardWindow" }.sorted(by: { (w1, w2) -> Bool in
            return w1.windowLevel < w2.windowLevel
        }).last
        // save the top window's tint color
        let savedTintColor = alertWindow?.tintColor
        alertWindow?.tintColor = UIApplication.shared.keyWindow?.tintColor

        // walk up the presentation tree
        var viewController = alertWindow?.rootViewController
        while viewController?.presentedViewController != nil {
            viewController = viewController?.presentedViewController
        }

        viewController?.present(self, animated: true, completion: nil)
        // restore the top window's tint color
        if let tintColor = savedTintColor {
            alertWindow?.tintColor = tintColor
        }
    }
}

上述代码唯一不太好的部分是它检查类名UIRemoteKeyboardWindow,以确保我们也可以包括它。尽管如此,上面的代码在iOS 9、10和11 GM种子中工作得很好,有正确的色调颜色,没有键盘滑动工件。

Kevin Sliech提供了一个很好的解决方案。

我现在在我的主UIViewController子类中使用下面的代码。

我做的一个小改动是检查最好的表示控制器是不是一个普通的UIViewController。如果不是,那就一定是某个VC表示一个普通VC。因此,我们返回正在呈现的VC。

- (UIViewController *)bestPresentationController
{
    UIViewController *bestPresentationController = [UIApplication sharedApplication].keyWindow.rootViewController;

    if (![bestPresentationController isMemberOfClass:[UIViewController class]])
    {
        bestPresentationController = bestPresentationController.presentedViewController;
    }    

    return bestPresentationController;
}

在我的测试中似乎一切正常。

谢谢你,凯文!

我知道这是针对iOS的,因为搜索引擎中几乎所有的链接都能找到iOS的帖子,我想我应该把这个提供给macOS的开发者。

macOS上的Swift 5.5

根据Darkngs的回答,我在另一个类中添加了这个方法:

let alert = NSAlert()
let viewController = NSApplication.shared.keyWindow?.contentViewController
alert.messageText = "An Alert Message."
alert.addButton(withTitle: "Ok")
alert.beginSheetModal(for: (viewController?.view.window)!) {
    (returnCode: NSApplication.ModalResponse) -> Void in
}

斯威夫特 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
    }
  }

}