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

时间:2014-10-24 19:24:49

标签: ios uialertcontroller

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

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

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

在这种情况下,您如何在UIAlertController中展示myUtilityMethod

36 个答案:

答案 0 :(得分:304)

在WWDC,我在其中一个实验室停留,向Apple工程师询问了同样的问题:"显示LOG: skipping missing configuration file "/Library/PostgreSQL/9.4/data/postgresql.auto.conf" pg_ctl: could not send stop signal (PID: 83): Operation not permitted 的最佳做法是什么?"而且他说他们已经得到了很多这个问题,我们开玩笑说他们应该有一个会议。他说内部Apple正在创建一个透明UIAlertController的{​​{1}},然后在其上展示UIWindow。基本上是Dylan Betterman的答案。

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

以下是相关代码:

UIAlertController

以下是一个示例用法:

show

#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 被取消分配后,将销毁// 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]; ,因为它是唯一保留UIWindow的对象。但是,如果您通过访问其中一个操作块中的警报将UIAlertController分配给某个属性或导致其保留计数增加,UIWindow将保留在屏幕上,从而锁定您的用户界面。请参阅上面的示例用法代码,以避免需要访问UIAlertController

我用测试项目制作了一个GitHub仓库:FFGlobalAlertController

答案 1 :(得分:94)

您可以使用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)

答案 2 :(得分:89)

<强>夫特

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)

<强>目标C

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

答案 3 :(得分:33)

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

<强>用法:

//option 1:
myAlertController.show()
//option 2:
myAlertController.present(animated: true) {
    //completion code...
}

这是扩展名:

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

答案 4 :(得分:31)

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

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

当您想要显示您的UIAlertController时:

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

    有几点需要注意:

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

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

    https://github.com/dbettermann/DBAlertController

答案 5 :(得分:24)

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

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

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

答案 6 :(得分:22)

以下解决方案确实工作,即使它对所有版本看起来都很有希望。 This solution is generating WARNING

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

https://stackoverflow.com/a/34487871/2369867 =&gt; 这看起来很有希望。但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)

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

答案 7 :(得分:18)

这适用于普通视图控制器的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)

答案 8 :(得分:17)

此处mythicalcoder's answer作为扩展程序,已经过测试和评估。在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)
    }

}

使用示例:

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

答案 9 :(得分:13)

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

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

这解决了我所遇到的根VC导致另一个VC的问题,而不是出现警报控制器,发出了上面报告的警告:

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

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

答案 10 :(得分:7)

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

LoginForm

答案 11 :(得分: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

并实施:* .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

您在此实施文件中使用此扩展程序,如下所示:

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

答案 12 :(得分:4)

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

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

9/15/2017更新:

测试并确认上述逻辑在新推出的iOS 11 GM种子中仍然有效。然而,agilityvision的最高投票方法不是:新铸造的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种子中运行良好,颜色正确,没有键盘滑动伪影。

答案 13 :(得分:3)

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

}

答案 14 :(得分:3)

对于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?()
        }
    }
}

您可以按原样使用它,或者如果您想在UIAlertController上使用便捷方法,可以将其放在扩展名中:

extension UIAlertController {

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

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

答案 15 :(得分:3)

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

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

alertControllerUIAlertController对象。

注意:您还需要确保您的帮助程序类扩展UIViewController

答案 16 :(得分:2)

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

答案 17 :(得分:2)

iOS13场景支持(使用UIWindowScene时)

import UIKit

private var windows: [String:UIWindow] = [:]

extension UIWindowScene {
    static var focused: UIWindowScene? {
        return UIApplication.shared.connectedScenes
            .first { $0.activationState == .foregroundActive && $0 is UIWindowScene } as? UIWindowScene
    }
}

class StyledAlertController: UIAlertController {

    var wid: String?

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

        //let window = UIWindow(frame: UIScreen.main.bounds)
        guard let window = UIWindowScene.focused.map(UIWindow.init(windowScene:)) else {
            return
        }
        window.rootViewController = UIViewController()
        window.windowLevel = .alert + 1
        window.makeKeyAndVisible()
        window.rootViewController!.present(self, animated: animated, completion: completion)

        wid = UUID().uuidString
        windows[wid!] = window
    }

    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        if let wid = wid {
            windows[wid] = nil
        }

    }

}

答案 18 :(得分:2)

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)

需要注意的一点是,如果当前正在显示UIAlertController,UIApplication.topMostViewController将返回UIAlertController。在UIAlertController之上呈现有奇怪的行为,应该避免。因此,您应该在展示之前手动检查!(UIApplication.topMostViewController is UIAlertController),或者添加else if个案例,如果self is UIAlertController

,则返回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
        }
    }
}

答案 19 :(得分:1)

您可以将当前视图或控制器作为参数发送:

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

答案 20 :(得分:1)

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

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

答案 21 :(得分:1)

除了给出了很好的答案(agilityvisionadibmalhal)。要像在旧的UIAlertViews中那样达到排队行为(避免警报窗口重叠),请使用此块来观察窗口级别的可用性:

@interface UIWindow (WLWindowLevel)

+ (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block;

@end

@implementation UIWindow (WLWindowLevel)

+ (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block {
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
    if (keyWindow.windowLevel == level) {
        // window level is occupied, listen for windows to hide
        id observer;
        observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIWindowDidBecomeHiddenNotification object:keyWindow queue:nil usingBlock:^(NSNotification *note) {
            [[NSNotificationCenter defaultCenter] removeObserver:observer];
            [self notifyWindowLevelIsAvailable:level withBlock:block]; // recursive retry
        }];

    } else {
        block(); // window level is available
    }
}

@end

完整示例:

[UIWindow notifyWindowLevelIsAvailable:UIWindowLevelAlert withBlock:^{
    UIWindow *alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    alertWindow.windowLevel = UIWindowLevelAlert;
    alertWindow.rootViewController = [UIViewController new];
    [alertWindow makeKeyAndVisible];

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:nil preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        alertWindow.hidden = YES;
    }]];

    [alertWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
}];

这样可以避免警报窗口重叠。可以使用相同的方法为任意数量的窗口层分离和放入队列视图控制器。

答案 22 :(得分:1)

已更新为可与iOS 13 Scenes一起使用,这打破了新的UIWindow方法。 Swift 5.1。

A <- data.frame(a=1:10)
B <- data.frame(a=2:11)

list_df <- list(sheet_A = A, sheet_B = B)
openxlsx::write.xlsx(list_df, 'A.xlsx')

答案 23 :(得分:1)

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

在我的测试中到目前为止似乎已经完成了所有工作。

谢谢凯文!

答案 24 :(得分:0)

另一个选择:

    var topController:UIViewController = UIApplication.shared.keyWindow!.rootViewController!
    while ((topController.presentedViewController) != nil) {
        topController = topController.presentedViewController!
    }
    topController.present(alert, animated:true, completion:nil)

答案 25 :(得分:0)

其中一些答案仅对我有用,将它们结合到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;
    if (rootViewController.presentedViewController != nil)
        rootViewController = rootViewController.presentedViewController;
    return rootViewController;
}

用法:

[[AppDelegate rootViewController] presentViewController ...

答案 26 :(得分:0)

如果应用的部署目标在iOS 13之上,则必须从SceneDelegate获取窗口。使用以下代码获取窗口:

guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
            let sceneDelegate = windowScene.delegate as? SceneDelegate
          else {
            return
          }
        
        let window = sceneDelegate.window

然后,使用下面的代码获取最顶级的视图控制器:

func topViewController(controller: UIViewController?) -> UIViewController? {
            if let navigationController = controller as? UINavigationController {
                return topViewController(controller: navigationController.visibleViewController)
            }
            if let tabController = controller as? UITabBarController {
                if let selected = tabController.selectedViewController {
                    return topViewController(controller: selected)
                }
            }
            if let presented = controller?.presentedViewController {
                return topViewController(controller: presented)
            }
            return controller
        }

然后,按如下所示使用它:

self.topViewController(controller: window?.rootViewController)?.present(self, animated: animated, completion: completion)

答案 27 :(得分:0)

在Swift 3中

let alertLogin = UIAlertController.init(title: "Your Title", message:"Your message", preferredStyle: .alert)
                                    alertLogin.addAction(UIAlertAction(title: "Done", style:.default, handler: { (AlertAction) in

                                    }))
                                    self.window?.rootViewController?.present(alertLogin, animated: true, completion: nil)

答案 28 :(得分:0)

我尝试了所提到的一切,但没有成功。我用于Swift 3.0的方法:

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

    func present(animated: Bool, completion: (() -> Void)?) {
        if var topController = UIApplication.shared.keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            topController.present(self, animated: animated, completion: completion)
        }
    }
}

答案 29 :(得分:0)

@ agilityvision的答案非常好。我对swift项目有用,所以我想我会用swift 3.0分享他对答案的看法

fileprivate class MyUIAlertController: UIAlertController {

  typealias Handler = () -> Void

  struct AssociatedKeys {
    static var alertWindowKey = "alertWindowKey"
  }

  dynamic var _alertWindow: UIWindow?

  var alertWindow: UIWindow? {
    return objc_getAssociatedObject(self, &AssociatedKeys.alertWindowKey) as? UIWindow
  }


  func setAlert(inWindow window: UIWindow) {
    objc_setAssociatedObject(self, &AssociatedKeys.alertWindowKey, _alertWindow, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  }

  func show(completion: Handler? = nil) {
    show(animated: true, completion: completion)
  }

  func show(animated: Bool, completion: Handler? =  nil) {
    _alertWindow = UIWindow(frame: UIScreen.main.bounds)
    _alertWindow?.rootViewController = UIViewController()

    if let delegate: UIApplicationDelegate = UIApplication.shared.delegate, let window = delegate.window {
      _alertWindow?.tintColor = window?.tintColor

    }

    let topWindow = UIApplication.shared.windows.last
    _alertWindow?.windowLevel = topWindow?.windowLevel ?? 0 + 1
    _alertWindow?.makeKeyAndVisible()
    _alertWindow?.rootViewController?.present(self, animated: animated, completion: completion)
  }

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

答案 30 :(得分:0)

创建助手类AlertWindow,而不是用作

let alertWindow = AlertWindow();
let alert = UIAlertController(title: "Hello", message: "message", preferredStyle: .alert);
let cancel = UIAlertAction(title: "Ok", style: .cancel){(action) in

    //....  action code here

    // reference to alertWindow retain it. Every action must have this at end

    alertWindow.isHidden = true;

   //  here AlertWindow.deinit{  }

}
alert.addAction(cancel);
alertWindow.present(alert, animated: true, completion: nil)


class AlertWindow:UIWindow{

    convenience init(){
        self.init(frame:UIScreen.main.bounds);
    }

    override init(frame: CGRect) {
        super.init(frame: frame);
        if let color = UIApplication.shared.delegate?.window??.tintColor {
            tintColor = color;
        }
        rootViewController = UIViewController()
        windowLevel = UIWindowLevelAlert + 1;
        makeKeyAndVisible()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    deinit{
        //  semaphor.signal();
    }

    func present(_ ctrl:UIViewController, animated:Bool, completion: (()->Void)?){
        rootViewController!.present(ctrl, animated: animated, completion: completion);
    }
}

答案 31 :(得分:0)

似乎工作:

static UIViewController *viewControllerForView(UIView *view) {
    UIResponder *responder = view;
    do {
        responder = [responder nextResponder];
    }
    while (responder && ![responder isKindOfClass:[UIViewController class]]);
    return (UIViewController *)responder;
}

-(void)showActionSheet {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
    [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
    [alertController addAction:[UIAlertAction actionWithTitle:@"Do it" style:UIAlertActionStyleDefault handler:nil]];
    [viewControllerForView(self) presentViewController:alertController animated:YES completion:nil];
}

答案 32 :(得分:0)

我在AppDelegate类

中使用了这些代码和一些个人变体
-(UIViewController*)presentingRootViewController
{
    UIViewController *vc = self.window.rootViewController;
    if ([vc isKindOfClass:[UINavigationController class]] ||
        [vc isKindOfClass:[UITabBarController class]])
    {
        // filter nav controller
        vc = [AppDelegate findChildThatIsNotNavController:vc];
        // filter tab controller
        if ([vc isKindOfClass:[UITabBarController class]]) {
            UITabBarController *tbc = ((UITabBarController*)vc);
            if ([tbc viewControllers].count > 0) {
                vc = [tbc viewControllers][tbc.selectedIndex];
                // filter nav controller again
                vc = [AppDelegate findChildThatIsNotNavController:vc];
            }
        }
    }
    return vc;
}
/**
 *   Private helper
 */
+(UIViewController*)findChildThatIsNotNavController:(UIViewController*)vc
{
    if ([vc isKindOfClass:[UINavigationController class]]) {
        if (((UINavigationController *)vc).viewControllers.count > 0) {
            vc = [((UINavigationController *)vc).viewControllers objectAtIndex:0];
        }
    }
    return vc;
}

答案 33 :(得分:0)

您可以使用以下两种方法:

- 使用pip install --upgrade beautifulsoup4或'UIActionSheet'(不推荐,因为它在iOS 8中已弃用但现在可以使用)

- 以某种方式记住最后一个视图控制器。这是一个例子。

UIAlertView

用法:

@interface UIViewController (TopController)
+ (UIViewController *)topViewController;
@end

// implementation

#import "UIViewController+TopController.h"
#import <objc/runtime.h>

static __weak UIViewController *_topViewController = nil;

@implementation UIViewController (TopController)

+ (UIViewController *)topViewController {
    UIViewController *vc = _topViewController;
    while (vc.parentViewController) {
        vc = vc.parentViewController;
    }
    return vc;
}

+ (void)load {
    [super load];
    [self swizzleSelector:@selector(viewDidAppear:) withSelector:@selector(myViewDidAppear:)];
    [self swizzleSelector:@selector(viewWillDisappear:) withSelector:@selector(myViewWillDisappear:)];
}

- (void)myViewDidAppear:(BOOL)animated {
    if (_topViewController == nil) {
        _topViewController = self;
    }

    [self myViewDidAppear:animated];
}

- (void)myViewWillDisappear:(BOOL)animated {
    if (_topViewController == self) {
        _topViewController = nil;
    }

    [self myViewWillDisappear:animated];
}

+ (void)swizzleSelector:(SEL)sel1 withSelector:(SEL)sel2
{
    Class class = [self class];

    Method originalMethod = class_getInstanceMethod(class, sel1);
    Method swizzledMethod = class_getInstanceMethod(class, sel2);

    BOOL didAddMethod = class_addMethod(class,
                                        sel1,
                                        method_getImplementation(swizzledMethod),
                                        method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
                            sel2,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end 

答案 34 :(得分:0)

您可以尝试使用mehtod在UIViewController上实现类别 - (void)presentErrorMessage;在该方法中,您实现了UIAlertController,然后将其呈现在self上。在您的客户端代码中,您将拥有类似的内容:

[myViewController presentErrorMessage];

通过这种方式,您可以避免不必要的参数和关于视图不在窗口层次结构中的警告。

答案 35 :(得分:-2)

在调用类方法之前注册通知。

Swift代码:

NSNotificationCenter.defaultCenter().addObserver(self, selector: "displayAlert", name: "ErrorOccured", object: nil)

displayAlert实例方法中,您可以显示提醒。