如何在不在视图控制器中时呈现UIAlertController?

Mur*_*gal 243 ios uialertcontroller

场景:用户点击视图控制器上的按钮.视图控制器是导航堆栈中最顶层的(显然).tap会调用另一个类上调用的实用程序类方法.在那里发生了一件坏事,我希望在控制返回到视图控制器之前在那里显示警报.

+ (void)myUtilityMethod {
    // do stuff
    // something bad happened, display an alert.
}
Run Code Online (Sandbox Code Playgroud)

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

在这种情况下,你如何呈现一个UIAlertController,就在那里myUtilityMethod

agi*_*ion 315

在WWDC,我在其中一个实验室停了下来,向苹果工程师询问了同样的问题:"展示一个实验室的最佳做法是UIAlertController什么?" 而且他说他们已经得到了很多这个问题,我们开玩笑说他们应该有一个会议.他说,内部苹果正在创建UIWindow一个透明的UIViewController,然后呈现UIAlertController它.基本上是Dylan Betterman的答案.

但我不想使用子类,UIAlertController因为这需要我在整个应用程序中更改我的代码.因此,在关联对象的帮助下,我创建了一个类别UIAlertController,show在Objective-C 中提供了一个方法.

这是相关代码:

#import "UIAlertController+Window.h"
#import <objc/runtime.h>

@interface UIAlertController (Window)

- (void)show;
- (void)show:(BOOL)animated;

@end

@interface UIAlertController (Private)

@property (nonatomic, strong) UIWindow *alertWindow;

@end

@implementation UIAlertController (Private)

@dynamic alertWindow;

- (void)setAlertWindow:(UIWindow *)alertWindow {
    objc_setAssociatedObject(self, @selector(alertWindow), alertWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIWindow *)alertWindow {
    return objc_getAssociatedObject(self, @selector(alertWindow));
}

@end

@implementation UIAlertController (Window)

- (void)show {
    [self show:YES];
}

- (void)show:(BOOL)animated {
    self.alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.alertWindow.rootViewController = [[UIViewController alloc] init];

    id<UIApplicationDelegate> delegate = [UIApplication sharedApplication].delegate;
    // Applications that does not load with UIMainStoryboardFile might not have a window property:
    if ([delegate respondsToSelector:@selector(window)]) {
        // we inherit the main window's tintColor
        self.alertWindow.tintColor = delegate.window.tintColor;
    }

    // window level is above the top window (this makes the alert, if it's a sheet, show over the keyboard)
    UIWindow *topWindow = [UIApplication sharedApplication].windows.lastObject;
    self.alertWindow.windowLevel = topWindow.windowLevel + 1;

    [self.alertWindow makeKeyAndVisible];
    [self.alertWindow.rootViewController presentViewController:self animated:animated completion:nil];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    // precaution to ensure window gets destroyed
    self.alertWindow.hidden = YES;
    self.alertWindow = nil;
}

@end
Run Code Online (Sandbox Code Playgroud)

以下是一个示例用法:

// need local variable for TextField to prevent retain cycle of Alert otherwise UIWindow
// would not disappear after the Alert was dismissed
__block UITextField *localTextField;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Global Alert" message:@"Enter some text" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
    NSLog(@"do something with text:%@", localTextField.text);
// do NOT use alert.textfields or otherwise reference the alert in the block. Will cause retain cycle
}]];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
    localTextField = textField;
}];
[alert show];
Run Code Online (Sandbox Code Playgroud)

UIWindow该时创建将被销毁UIAlertController的dealloced,因为它是被保留的唯一对象UIWindow.但是,如果您UIAlertController通过访问其中一个操作块中的警报来分配属性或导致其保留计数增加,UIWindow则将保留在屏幕上,锁定您的UI.请参阅上面的示例用法代码,以避免需要访问UITextField.

我用一个测试项目做了一个GitHub repo:FFGlobalAlertController

  • 在类别上实现`viewDidDisappear:`看起来像一个坏主意.实质上,您正在与框架的`viewDidDisappear:`的实现竞争.现在它可能没问题,但如果Apple决定将来实现该方法,那么就没有办法让它调用它(即没有类似的`super`指向一个方法的主要实现类别实施). (17认同)
  • 工作得很好,但如何处理`prefersStatusBarHidden`和`preferredStatusBarStyle`没有额外的子类? (5认同)
  • 这段代码现在似乎在 iOS 13 中不起作用。在我的例子中,警报根本没有显示。 (3认同)
  • 我真的很喜欢您的回答的优雅,但是我很好奇您如何停用新窗口并再次使原始窗口成为关键(诚然,我并没有过多地处理窗口)。 (2认同)
  • 关键窗口是最上面的可见窗口,所以我的理解是如果你删除/隐藏“关键”窗口,下一个可见窗口就变成了“关键”。 (2认同)

Zev*_*erg 101

您可以使用Swift 2.2执行以下操作:

let alertController: UIAlertController = ...
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)

和Swift 3.0:

let alertController: UIAlertController = ...
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)

  • 我在控制台中注意到:`警告:尝试在<UINavigationController:0x1458e450>上显示<UIAlertController:0x145bfa30>,其视图不在窗口层次结构中!`. (21认同)
  • 哎呀,我在检查前接受了.该代码返回根视图控制器,在我的例子中是导航控制器.它不会导致错误,但不会显示警报. (12认同)
  • 我这样做是因为我不想接受别人的工作.这是我为swift 3.0修改的@ZevEisenberg的解决方案.如果我想添加另一个答案,那么我可能会得到他应得的投票. (2认同)

Dar*_*ngs 97

Objective-C的

let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
//...
var rootViewController = UIApplication.shared.keyWindow?.rootViewController
if let navigationController = rootViewController as? UINavigationController {
    rootViewController = navigationController.viewControllers.first
}
if let tabBarController = rootViewController as? UITabBarController {
    rootViewController = tabBarController.selectedViewController
}
//...
rootViewController?.present(alertController, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)

斯威夫特2.3

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Title" message:@"message" preferredStyle:UIAlertControllerStyleAlert];
//...
id rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
if([rootViewController isKindOfClass:[UINavigationController class]])
{
    rootViewController = ((UINavigationController *)rootViewController).viewControllers.firstObject;
}
if([rootViewController isKindOfClass:[UITabBarController class]])
{
    rootViewController = ((UITabBarController *)rootViewController).selectedViewController;
}
//...
[rootViewController presentViewController:alertController animated:YES completion:nil];
Run Code Online (Sandbox Code Playgroud)

斯威夫特3

let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
//...
var rootViewController = UIApplication.shared.keyWindow?.rootViewController
if let navigationController = rootViewController as? UINavigationController {
    rootViewController = navigationController.viewControllers.first
}
if let tabBarController = rootViewController as? UITabBarController {
    rootViewController = tabBarController.selectedViewController
}
//...
rootViewController?.present(alertController, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)

  • 很好,你可能想要添加另一部分:if(rootViewController.presentedViewController!= nil){rootViewController = rootViewController.presentedViewController; } (8认同)
  • +1这是一个非常简单的解决方案.(我遇到的问题:在Master/Detail模板的DetailViewController中显示警报 - 在iPad上显示,从不在iPhone上显示) (2认同)
  • Swift 5.2:Xcode 现在表示 UIApplication.shared.keyWindow 自 iOS 13.0 以来已被弃用 (2认同)

Avi*_*oss 33

UIAlertController extension对于所有UINavigationController和/或的案例都非常通用UITabBarController.如果此时屏幕上有模态VC,也可以使用.

用法:

//option 1:
myAlertController.show()
//option 2:
myAlertController.present(animated: true) {
    //completion code...
}
Run Code Online (Sandbox Code Playgroud)

这是扩展:

//Uses Swift1.2 syntax with the new if-let
// so it won't compile on a lower version.
extension UIAlertController {

    func show() {
        present(animated: true, completion: nil)
    }

    func present(#animated: Bool, completion: (() -> Void)?) {
        if let rootVC = UIApplication.sharedApplication().keyWindow?.rootViewController {
            presentFromController(rootVC, animated: animated, completion: completion)
        }
    }

    private func presentFromController(controller: UIViewController, animated: Bool, completion: (() -> Void)?) {
        if  let navVC = controller as? UINavigationController,
            let visibleVC = navVC.visibleViewController {
                presentFromController(visibleVC, animated: animated, completion: completion)
        } else {
          if  let tabVC = controller as? UITabBarController,
              let selectedVC = tabVC.selectedViewController {
                presentFromController(selectedVC, animated: animated, completion: completion)
          } else {
              controller.presentViewController(self, animated: animated, completion: completion)
          }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Dyl*_*ann 31

几个月前我发布了一个类似的问题,并认为我终于解决了这个问题.如果您只想查看代码,请点击我帖子底部的链接.

解决方案是使用额外的UIWindow.

当您想要显示您的UIAlertController时:

  1. 使您的窗口成为关键和可见窗口(window.makeKeyAndVisible())
  2. 只需使用普通的UIViewController实例作为新窗口的rootViewController.(window.rootViewController = UIViewController())
  3. 在窗口的rootViewController上显示您的UIAlertController

有几点需要注意:

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

最后,如果您只想查看,我已经完成了实施.

https://github.com/dbettermann/DBAlertController

  • 是的,它甚至适用于Swift 2.0/iOS 9.我现在正在开发一个Objective-C版本,因为其他人要求它(也许是你).我完成后会回复. (2认同)

adi*_*dib 26

改进agilityvision的答案,您需要创建一个带有透明根视图控制器的窗口,并从那里显示警报视图.

但是,只要您在警报控制器中有操作,就不需要保留对窗口的引用.作为操作处理程序块的最后一步,您只需要将窗口隐藏为清理任务的一部分.通过在处理程序块中引用窗口,这将创建一个临时循环引用,一旦警报控制器被关闭,它将被破坏.

UIWindow* window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.rootViewController = [UIViewController new];
window.windowLevel = UIWindowLevelAlert + 1;

UIAlertController* alertCtrl = [UIAlertController alertControllerWithTitle:... message:... preferredStyle:UIAlertControllerStyleAlert];

[alertCtrl addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK",@"Generic confirm") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    ... // do your stuff

    // very important to hide the window afterwards.
    // this also keeps a reference to the window until the action is invoked.
    window.hidden = YES;
}]];

[window makeKeyAndVisible];
[window.rootViewController presentViewController:alertCtrl animated:YES completion:nil];
Run Code Online (Sandbox Code Playgroud)


myt*_*der 24

以下解决方案虽然在所有版本中看起来很有前景,但仍无法正常工作.此解决方案正在生成警告.

警告:尝试显示其视图不在窗口层次结构中!

/sf/answers/2414151001/ =>这看起来很有希望.但它是不是Swift 3.所以我在Swift 3中回答这个问题,这不是模板示例.

一旦粘贴到任何函数中,这本身就是功能完备的代码.

快速Swift 3 自包含代码

let alertController = UIAlertController(title: "<your title>", message: "<your message>", preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.cancel, handler: nil))

let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alertController, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)

这是Swift 3中经过测试和运行的代码.

  • 提醒一下:您需要存储对您的“UIWindow”的强引用,否则窗口将被释放并在超出范围后不久消失。 (3认同)

bob*_*ehm 23

这是mythicalcoder的答案作为扩展,在Swift 4中测试和工作:

extension UIAlertController {

    func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) {
        let alertWindow = UIWindow(frame: UIScreen.main.bounds)
        alertWindow.rootViewController = UIViewController()
        alertWindow.windowLevel = UIWindowLevelAlert + 1;
        alertWindow.makeKeyAndVisible()
        alertWindow.rootViewController?.present(self, animated: animated, completion: completion)
    }

}
Run Code Online (Sandbox Code Playgroud)

用法示例:

let alertController = UIAlertController(title: "<Alert Title>", message: "<Alert Message>", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
alertController.presentInOwnWindow(animated: true, completion: {
    print("completed")
})
Run Code Online (Sandbox Code Playgroud)


Wil*_*ken 20

这适用于普通视图控制器的Swift,即使屏幕上有导航控制器:

let alert = UIAlertController(...)

let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.presentViewController(alert, animated: true, completion: nil)
Run Code Online (Sandbox Code Playgroud)


Kev*_*ech 13

再加上Zev的答案(并切换回Objective-C),您可能会遇到根视图控制器通过segue或其他东西呈现其他VC的情况.在根VC上调用presentViewController将处理这个:

[[UIApplication sharedApplication].keyWindow.rootViewController.presentedViewController presentViewController:alertController animated:YES completion:^{}];
Run Code Online (Sandbox Code Playgroud)

这解决了我所遇到的一个问题,即根VC已经将其隐藏到另一个VC,并且没有出现警报控制器,而是发出了上面报告的警告:

Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!
Run Code Online (Sandbox Code Playgroud)

我没有测试过,但如果您的根VC恰好是导航控制器,这可能也是必要的.

  • @Mayerz将Objective-C翻译成Swift应该不是什么大问题;)但是你在这里:`UIApplication.sharedApplication().keyWindow?.rootViewController?.presentsentController?.presentViewController(controller,animated:true,completion:nil )` (2认同)
  • 我使用相同的方法,如果它不是nil,使用`rootViewController.presentedViewController`,否则使用`rootViewController`.对于一个完全通用的解决方案,可能需要遍历`presentsViewController的链来获取`topmost` VC (2认同)

小智 8

@ agilityvision的答案翻译成Swift4/iOS11.我没有使用本地化字符串,但您可以轻松地更改它:

import UIKit

/** An alert controller that can be called without a view controller.
 Creates a blank view controller and presents itself over that
 **/
class AlertPlusViewController: UIAlertController {

    private var alertWindow: UIWindow?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.alertWindow?.isHidden = true
        alertWindow = nil
    }

    func show() {
        self.showAnimated(animated: true)
    }

    func showAnimated(animated _: Bool) {

        let blankViewController = UIViewController()
        blankViewController.view.backgroundColor = UIColor.clear

        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = blankViewController
        window.backgroundColor = UIColor.clear
        window.windowLevel = UIWindowLevelAlert + 1
        window.makeKeyAndVisible()
        self.alertWindow = window

        blankViewController.present(self, animated: true, completion: nil)
    }

    func presentOkayAlertWithTitle(title: String?, message: String?) {

        let alertController = AlertPlusViewController(title: title, message: message, preferredStyle: .alert)
        let okayAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
        alertController.addAction(okayAction)
        alertController.show()
    }

    func presentOkayAlertWithError(error: NSError?) {
        let title = "Error"
        let message = error?.localizedDescription
        presentOkayAlertWithTitle(title: title, message: message)
    }
}
Run Code Online (Sandbox Code Playgroud)


Tim*_*ich 7

斯威夫特 4+

我使用了多年的解决方案,没有任何问题。首先我扩展UIWindow找到它的visibleViewController。注意:如果您使用自定义集合*类(例如侧面菜单),您应该在以下扩展中添加针对这种情况的处理程序。获得最顶层的视图控制器后,很容易呈现,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
    }
  }

}
Run Code Online (Sandbox Code Playgroud)


Log*_*ier 7

对于iOS 13,建立由答案mythicalcoderbobbyrehm

在 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?()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以按原样使用它,或者如果你想要一个方便的方法UIAlertController,你可以把它放在一个扩展中:

extension UIAlertController {

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

        let windowAlertPresentationController = WindowAlertPresentationController(alert: self)
        windowAlertPresentationController.present(animated: animated, completion: completion)
    }
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*sta 6

像Aviel Gross一样创建扩展.这里你有Objective-C扩展.

这里有头文件*.h

//  UIAlertController+Showable.h

#import <UIKit/UIKit.h>

@interface UIAlertController (Showable)

- (void)show;

- (void)presentAnimated:(BOOL)animated
             completion:(void (^)(void))completion;

- (void)presentFromController:(UIViewController *)viewController
                     animated:(BOOL)animated
                   completion:(void (^)(void))completion;

@end
Run Code Online (Sandbox Code Playgroud)

并实施:*.m

//  UIAlertController+Showable.m

#import "UIAlertController+Showable.h"

@implementation UIAlertController (Showable)

- (void)show
{
    [self presentAnimated:YES completion:nil];
}

- (void)presentAnimated:(BOOL)animated
             completion:(void (^)(void))completion
{
    UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController;
    if (rootVC != nil) {
        [self presentFromController:rootVC animated:animated completion:completion];
    }
}

- (void)presentFromController:(UIViewController *)viewController
                     animated:(BOOL)animated
                   completion:(void (^)(void))completion
{

    if ([viewController isKindOfClass:[UINavigationController class]]) {
        UIViewController *visibleVC = ((UINavigationController *)viewController).visibleViewController;
        [self presentFromController:visibleVC animated:animated completion:completion];
    } else if ([viewController isKindOfClass:[UITabBarController class]]) {
        UIViewController *selectedVC = ((UITabBarController *)viewController).selectedViewController;
        [self presentFromController:selectedVC animated:animated completion:completion];
    } else {
        [viewController presentViewController:self animated:animated completion:completion];
    }
}

@end
Run Code Online (Sandbox Code Playgroud)

您在您的实现文件中使用此扩展名,如下所示:

#import "UIAlertController+Showable.h"

UIAlertController* alert = [UIAlertController
    alertControllerWithTitle:@"Title here"
                     message:@"Detail message here"
              preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction* defaultAction = [UIAlertAction
    actionWithTitle:@"OK"
              style:UIAlertActionStyleDefault
            handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];

// Add more actions if needed

[alert show];
Run Code Online (Sandbox Code Playgroud)


exp*_*exp 6

斯威夫特 5

显示消息后隐藏窗口很重要。

func showErrorMessage(_ message: String) {
    let alertWindow = UIWindow(frame: UIScreen.main.bounds)
    alertWindow.rootViewController = UIViewController()

    let alertController = UIAlertController(title: "Error", message: message, preferredStyle: UIAlertController.Style.alert)
    alertController.addAction(UIAlertAction(title: "Close", style: UIAlertAction.Style.cancel, handler: { _ in
        alertWindow.isHidden = true
    }))
    
    alertWindow.windowLevel = UIWindow.Level.alert + 1;
    alertWindow.makeKeyAndVisible()
    alertWindow.rootViewController?.present(alertController, animated: true, completion: nil)
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*ick 5

交叉发布我的答案,因为这两个线程没有被标记为欺骗......

现在它UIViewController是响应者链的一部分,您可以执行以下操作:

if let vc = self.nextResponder()?.targetForAction(#selector(UIViewController.presentViewController(_:animated:completion:)), withSender: self) as? UIViewController {

    let alert = UIAlertController(title: "A snappy title", message: "Something bad happened", preferredStyle: .Alert)
    alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))

    vc.presentViewController(alert, animated: true, completion: nil)
}
Run Code Online (Sandbox Code Playgroud)


Maj*_*ter 5

如果有人感兴趣,我创建了一个 Swift 3 版本的 @agilityvision 答案。代码:

import Foundation
import UIKit

extension UIAlertController {

    var window: UIWindow? {
        get {
            return objc_getAssociatedObject(self, "window") as? UIWindow
        }
        set {
            objc_setAssociatedObject(self, "window", newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.window?.isHidden = true
        self.window = nil
    }

    func show(animated: Bool = true) {
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = UIViewController(nibName: nil, bundle: nil)

        let delegate = UIApplication.shared.delegate
        if delegate?.window != nil {
            window.tintColor = delegate!.window!!.tintColor
        }

        window.windowLevel = UIApplication.shared.windows.last!.windowLevel + 1

        window.makeKeyAndVisible()
        window.rootViewController!.present(self, animated: animated, completion: nil)

        self.window = window
    }
}
Run Code Online (Sandbox Code Playgroud)


Cod*_*rew 5

Zev Eisenberg 的答案简单明了,但并不总是有效,并且可能会失败并显示以下警告消息:

Warning: Attempt to present <UIAlertController: 0x7fe6fd951e10>  
 on <ThisViewController: 0x7fe6fb409480> which is already presenting 
 <AnotherViewController: 0x7fe6fd109c00>
Run Code Online (Sandbox Code Playgroud)

这是因为 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)
}
Run Code Online (Sandbox Code Playgroud)

2017 年 9 月 15 日更新:

测试并确认上述逻辑在新推出的 iOS 11 GM 种子中仍然有效。然而,agilityvision 投票最高的方法却并非如此:新创建的警报视图UIWindow位于键盘下方,可能会阻止用户点击其按钮。这是因为在 iOS 11 中,所有高于键盘窗口的 windowLevel 都会降低到低于它的级别。

不过,呈现的一个工件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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

上面代码中唯一不太重要的部分是它检查类名UIRemoteKeyboardWindow以确保我们也可以包含它。尽管如此,上面的代码在 iOS 9、10 和 11 GM 种子中确实运行良好,具有正确的色调并且没有键盘滑动伪影。


Eer*_*rko 5

其中一些答案仅对我有用,将它们组合到 AppDelegate 中的以下类方法中就是我的解决方案。它适用于 iPad、UITabBarController 视图、UINavigationController 以及呈现模式时。在 iOS 10 和 13 上测试。

+ (UIViewController *)rootViewController {
    UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
    if([rootViewController isKindOfClass:[UINavigationController class]])
        rootViewController = ((UINavigationController *)rootViewController).viewControllers.firstObject;
    if([rootViewController isKindOfClass:[UITabBarController class]])
        rootViewController = ((UITabBarController *)rootViewController).selectedViewController;
    while (rootViewController.presentedViewController != nil)
        rootViewController = rootViewController.presentedViewController;
    return rootViewController;
}
Run Code Online (Sandbox Code Playgroud)

用法:

[[AppDelegate rootViewController] presentViewController ...
Run Code Online (Sandbox Code Playgroud)