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

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


UIAlertView可能发生这种情况(但可能不太正确) )。

在这种情况下,如何在UIAlertController中显示一个myUtilityMethod

#1 楼

我在几个月前发布了类似的问题,并认为我终于解决了这个问题。如果您只想查看代码,请单击文章底部的链接。

解决方案是使用其他UIWindow。

要显示UIAlertController时:


使窗口成为键和可见窗口(window.makeKeyAndVisible()
只需使用普通的UIViewController实例作为新窗口的rootViewController。 (window.rootViewController = UIViewController()
在窗口的rootViewController上呈现UIAlertController

有两点需要注意:


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

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

https://github.com/dbettermann/DBAlertController

评论


您没有Objective-C的功能吗?

– SAHM
15年6月23日在23:49

是的,它甚至可以在Swift 2.0 / iOS 9中运行。我正在开发一个Objective-C版本,因为有人要求它(也许是您)。完成后我会回发。

–迪伦·贝特曼(Dylan Bettermann)
15年6月24日在2:49

#2 楼

在WWDC上,我在其中一个实验室停下来,并向苹果工程师问了同样的问题:“显示UIAlertController的最佳实践是什么?”他说,他们已经收到了很多这个问题,我们开玩笑说,他们应该参加一次会议。他说,Apple内部正在创建带有透明UIWindowUIViewController,然后在其上展示UIAlertController。基本上,Dylan Betterman的回答是什么。
但是我不想使用UIAlertController的子类,因为那将需要我在整个应用程序中更改代码。因此,借助相关对象,我在UIAlertController上创建了一个类别,该类别在Objective-C中提供了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。但是,如果您将UIWindow分配给某个属性,或者通过访问其中一个操作块中的警报来导致其保留计数增加,则UITextField将停留在屏幕上,从而锁定您的UI。请参阅上面的示例用法代码,以避免需要访问q4312079q的情况。
我通过测试项目FFGlobalAlertController
制作了GitHub存储库。

评论


好东西!只是一些背景-我使用子类而不是关联的对象,因为我使用的是Swift。关联对象是Objective-C运行时的功能,我不想依赖它。 Swift可能距离获得自己的运行时还需要几年的时间,但仍然如此。 :)

–迪伦·贝特曼(Dylan Bettermann)
2015年6月24日在3:15



我真的很喜欢您回答的优美性,但是我很好奇您如何退役新窗口并再次将原始窗口设置为键(顺便说一句,我不太在乎该窗口)。

– Dustin Pfannenstiel
2015年6月24日14:18在

关键窗口是最顶部的可见窗口,所以我的理解是,如果您删除/隐藏“关键”窗口,则下一个可见窗口将变为“关键”。

– Agilityvision
15年6月24日在22:47

在一个类别上实现viewDidDisappear:似乎是一个坏主意。本质上,您正在与viewDidDisappear:框架的实现竞争。现在也许还可以,但是如果Apple决定将来实现该方法,则您无法调用它(即,没有类似类的super指向类别实现中方法的主要实现)。

–adib
16年1月23日在6:11

效果很好,但是如何在没有额外的子类的情况下如何处理preferredStatusBarHidden和preferredStatusBarStyle?

–凯文·弗拉希曼(Kevin Flachsmann)
16 Jun 20'22:58



#3 楼

Swift

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)


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


评论


+1这是一个非常简单的解决方案。 (我面临的问题:在“主/明细”模板的DetailViewController中显示警报-在iPad上显示,在iPhone上则不显示)

–大卫
16-2-25在19:16



很好,您可能需要添加另一部分:if(rootViewController.presentedViewController!= nil){rootViewController = rootViewController.presentedViewController; }

–DivideByZer0
16年5月25日在1:27



Swift 3:“警报”已重命名为“警报”:let alertController = UIAlertController(title:“ title”,message:“ message”,preferredStyle:.alert)

–机甲
16-11-27在11:41

请改用委托!

–安德鲁·基尔纳(Andrew Kirna)
19年5月16日在13:27

#4 楼

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


评论


糟糕,我在检查之前接受了。该代码返回根视图控制器,在我的情况下是导航控制器。它不会引起错误,但是不会显示警报。

– Murray Sagal
14-10-24在21:09

我在控制台中注意到:警告:尝试在视图不在窗口层次结构中的上显示

– Murray Sagal
14-10-24在21:21

@MurraySagal具有导航控制器,您可以随时获取visibleViewController属性,以查看从哪个控制器呈现警报。查看文档

– Lubo
16-4-6在13:30



我这样做是因为我不想对别人的工作表示赞赏。这是@ZevEisenberg的解决方案,我针对Swift 3.0进行了修改。如果我还要添加另一个答案,那么我可能会得到他应得的投票。

–jeet.chanchawat
16/09/13在18:17



哦,嘿,我错过了昨天的所有戏曲,但是我碰巧刚刚更新了Swift 3的帖子。我不知道SO在更新新语言版本的旧答案方面的政策是什么,但是我个人并不介意,只要答案是正确的!

–泽夫·艾森伯格
16/09/13在18:32

#5 楼

适用于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)
          }
        }
    }
}


评论


我正在使用此解决方案,但发现它确实非常完美,优雅,干净……但是,最近我不得不将根视图控制器更改为不在视图层次结构中的视图,因此该代码变得无用。有人想继续使用这个dix吗?

–user1585121
15年11月30日在16:30

我将这种解决方案与其他方法结合使用:我有一个单例UI类,它持有UIViewController类型的(弱!)currentVC。我具有从UIViewController继承的BaseViewController并将UI.currentVC设置为viewDidAppear上的self,然后在viewWillDisappear上设置为nil 。我在应用程序中的所有视图控制器都继承了BaseViewController。这样,如果UI.currentVC中有内容(不是nil ...)-绝对不在演示动画的中间,您可以要求它演示UIAlertController。

–航空总收入
15年11月30日在17:33



如下所示,根视图控制器可能会呈现某种顺序,在这种情况下,您的最后一个if语句将失败,因此我必须添加else {如果让presentedViewController = controller.presentedViewController {presentViewController.presentViewController(self,animation:其他{controller.presentViewController(自己,已动画:已动画,已完成:完成)}}

–尼古拉斯
16-4-8在11:26



#6 楼

要改善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];


评论


完美,正是我需要关闭窗户的小费,谢谢同伴

–蒂博特·诺亚
17年1月11日在17:08

#7 楼

以下解决方案即使在所有版本中都看起来很有希望,也无法正常工作。此解决方案正在生成警告。

警告:尝试呈现不在窗口层次结构中的视图!

https://stackoverflow.com/a/34487871/2369867 =>
那看起来很有希望。但是它不在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中经过测试并且可以工作

评论


在加载任何根视图控制器之前,在App Delegate中针对迁移问题触发了UIAlertController的情况下,这段代码对我来说非常有效。工作很好,没有警告。

–邓肯·巴贝奇(Duncan Babbage)
17年4月6日在21:52

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

–警笛声
19年6月20日在3:17

#8 楼

这是神话编码器的扩展,在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")
})


评论


即使sharedApplication无法访问,也可以使用它!

–艾哈迈德雷萨(Ahmadreza)
17-10-25在8:21

#9 楼

即使在屏幕上有导航控制器,它也可以在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)


评论


当我关闭警报时,UIWindow没有响应。可能与windowLevel有关。如何使其具有响应性?

–滑块
16-2-15在20:32



听起来好像没有开新窗口。

–伊戈尔·库拉金(Igor Kulagin)
16年6月20日在4:59

看起来Window并未从顶部移除,因此需要在完成后移除该窗口。

–苏安·塞尼
19年3月11日23:06

完成后将AlertWindow设置为nil。

– C6Silver
19-11-30在0:17



#10 楼

加上Zev的答案(然后切换回Objective-C),您可能会遇到这样的情况,即您的根视图控制器正在通过segue或其他方式呈现其他一些VC。在根VC上调用presentedViewController将解决此问题:

[[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恰好是导航控制器。

评论


哼,我在Swift中遇到了这个问题,但是我找不到如何将objc代码转换为swift的方法,我们将不胜感激!

–user1585121
15年11月30日在16:36

@Mayerz将Objective-C转换为Swift应该没什么大不了的;),但是您在这里:UIApplication.sharedApplication()。keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller,animation:true,completion:nil)

– Borchero
15年12月31日在1:26

感谢Olivier,您是对的,就像馅饼一样容易,我确实以这种方式进行了翻译,但是问题出在其他地方。不管怎么说,还是要谢谢你!

–user1585121
16年1月4日,9:50



不允许在取消分配时尝试加载视图控制器的视图,并且可能导致未定义的行为(

– Mojo66
16年4月19日在22:58

我采用了相同的方法,如果不是nil,请使用rootViewController.presentedViewController,否则请使用rootViewController。对于完全通用的解决方案,可能有必要遍历presentedViewControllers链以获得最高的VC

– Protongun
16年7月20日在2:48

#11 楼

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


评论


我的答案得到了黑色背景。 window.backgroundColor = UIColor.clear修复了该问题。 viewController.view.backgroundColor = UIColor.clear似乎不是必需的。

– Ben补丁
18-3-23在18:25



请记住,Apple警告UIAlertController子类化:UIAlertController类旨在按原样使用,不支持子类化。此类的视图层次结构是私有的,不能修改。 developer.apple.com/documentation/uikit/uialertcontroller

–格鲁巴斯
18年6月7日在18:55



#12 楼

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


评论


如果您需要手动消除警报,则此方法将不起作用-WindowAlertPresentationController永远不会取消分配,从而导致UI冻结-由于窗口仍然存在,因此无法进行交互

– JBlake
19-10-20在18:20

如果要手动关闭警报,请确保直接在WindowAlertPresentationController上调用dismiss alert.presentingViewController?.dismiss(动画:true,完成:nil)

– JBlake
19-10-20在18:25

让alertController = UIAlertController(title:“ title”,消息:“ message”,preferredStyle:.alert); alertController.presentInOwnWindow(动画:false,完成:nil)对我来说很棒!谢谢!

–布赖恩
19年11月22日15:59

这适用于装有iOS 12.4.5的iPhone 6,但不适用于装有iOS 13.3.1的iPhone 11 Pro。没有错误,但是警报从未显示。任何建议,将不胜感激。

– jl303
2月9日在19:03



在iOS 13上运行良好。在Catalyst中不起作用-消除警报后,该应用将无法进行交互。参见@Peter Lapisu的解决方案

– JBlake
3月21日15:23

#13 楼

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


#14 楼

交叉发布我的答案,因为这两个线程未标记为重复对象...

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

< pre class =“ lang-swift prettyprint-override”> 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) }

#15 楼

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


2017年9月15日更新:

经过测试并确认,以上逻辑在新提供的iOS 11 GM种子中仍然有效。但是,agilityvision投票最多的方法却没有:在新创建的UIWindow中显示的警报视图位于键盘下方,并且有可能阻止用户点击其按钮。这是因为在iOS 11中,高于键盘窗口的所有windowLevels都降低到低于它的水平。

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 { q4312078q.tintColor != nil || q4312078q.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)的答案,该答案正试图以类似的方法解决相同的问题,但是由于没有走上展示链,因此很容易遇到与尝试解决相同的错误。

– CodeBrew
17年8月9日14:28



#16 楼

Swift 4+

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

}


#17 楼

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

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


其中alertController是您的UIAlertController对象。

注意:您还将需要确保您的助手课程扩展了UIViewController

#18 楼

如果有人感兴趣,我创建了@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
    }
}


评论


@Chathuranga:我已还原您的编辑。完全没有必要进行“错误处理”。

–马丁R
18-09-26在7:17

#19 楼

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

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

,则在出现前手动检查self is UIAlertController是否返回nil

#20 楼

其中一些答案仅对我部分有效,将它们结合到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 ...


#21 楼

iOS13场景支持(使用UIWindowScene时)

import UIKit

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

extension UIWindowScene {
    static var focused: UIWindowScene? {
        return UIApplication.shared.connectedScenes
            .first { q4312078q.activationState == .foregroundActive && q4312078q 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
        }

    }

}


评论


UIAlerController不应根据文档developer.apple.com/documentation/uikit/uialertcontroller进行子类化

–权限
6月24日8:12



UIButton也不应被子类化,每个人都应该...只要您不修改私有方法并且不更改alertcontroller内部的视图,就可以了

– Peter Lapisu
6月26日7:29



我找不到对UIButton的任何引用也不应被子类化。能否请您分享来源?

–权限
6月28日15:40

谢谢!让UIScenes真的搞砸了如何创建窗口,您的做法很好。

–卡洛斯·丰塞卡(Carlos Fonseca)
10月22日15:35



#22 楼

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


#23 楼

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

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


评论


是的,这是可能的,并且会起作用。但是对我来说,它有点代码味道。通常,传递的参数是被调用方法执行其主要功能所必需的。另外,所有现有呼叫都需要进行修改。

– Murray Sagal
2015年4月3日13:48

#24 楼

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


到目前为止,在我的测试中似乎都可以解决。

谢谢Kevin!

#25 楼

除了给出很好的答案(agilityvision,adib,malhal)。要达到良好的旧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


完整示例: br />
[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];
}];


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

#26 楼

似乎可以工作:

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


评论


这实际上是巧妙的。在所有情况下可能都不是理想的选择,尤其是在您使用子视图控制器的情况下,理想情况下,该子视图控制器不应提供视图控制器,但是可以对此进行改进。

–奇怪的时间
7月24日14:52

#27 楼

我尝试了所有提到的方法,但没有成功。我用于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)
        }
    }
}


#28 楼

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

fileprivate var alertWindows = [UIAlertController:UIWindow]()

extension UIAlertController {

    func presentInNewWindow(animated: Bool, completion: (() -> Void)?) {
        let foregroundActiveScene = UIApplication.shared.connectedScenes.filter { q4312078q.activationState == .foregroundActive }.first
        guard let foregroundWindowScene = foregroundActiveScene as? UIWindowScene else { return }

        let window = UIWindow(windowScene: foregroundWindowScene)
        alertWindows[self] = window

        window.rootViewController = UIViewController()
        window.windowLevel = .alert + 1
        window.makeKeyAndVisible()
        window.rootViewController!.present( self, animated: animated, completion: completion)
    }

    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        alertWindows[self] = nil
    }

}


#29 楼

您可以尝试使用像
UIViewController这样的方法在- (void)presentErrorMessage;上实现一个类别,然后在该方法内实现UIAlertController,然后将其呈现在self上。比起您的客户端代码,您将得到类似:

[myViewController presentErrorMessage];

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

评论


除了我的代码中没有myViewController,这是一件不好的事情发生的地方。那是在实用程序方法中,它对调用它的视图控制器一无所知。

– Murray Sagal
2015年4月7日在16:34

恕我直言,向用户呈现任何视图(因此警报)是ViewControllers的责任。因此,如果代码的某些部分对viewController一无所知,则不应向用户呈现任何错误,而应将其传递给代码的“ viewController感知”部分

–弗拉德·索罗卡(Vlad Soroka)
15年4月7日在18:59

我同意。但是,现已弃用的UIAlertView的便利性使我在几个地方违反了该规则。

– Murray Sagal
15年4月7日在19:09

#30 楼

您可以使用2种方法:

-使用UIAlertView或'UIActionSheet'代替(不建议使用,因为它在iOS 8中已弃用,但现在可以使用)

-请记住最后显示的视图控制器。这是示例。

@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 


用法:

[[UIViewController topViewController] presentViewController:alertController ...];