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

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

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

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

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

179023 次浏览

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

斯威夫特代码:

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

displayAlert实例方法中,可以显示警报。

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

斯威夫特

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

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

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

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

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

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

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

+ (void)myUtilityMethod:(id)controller {
// do stuff
// something bad happened, display an alert.
}
你可以尝试在UIViewController上使用类似于 - (void)presentErrorMessage;和在该方法中实现UIAlertController,然后在self上显示它。然后在你的客户端代码中,你会有这样的东西:

[myViewController presentErrorMessage];

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

对于所有UINavigationController和/或UITabBarController的情况,非常通用的UIAlertController extension。如果屏幕上有一个模态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)
}
}
}
}

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

解决方案是使用一个额外的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

创建扩展像在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];

在WWDC上,我在一个实验室停下来,问了一个苹果工程师同样的问题:“显示UIAlertController的最佳实践是什么?”他说他们经常被问到这个问题,我们开玩笑说他们应该就此开个会。他说苹果内部正在创建一个带有透明UIViewControllerUIWindow,然后在上面显示UIAlertController。基本上就是迪伦·贝特曼的答案。

但我不想使用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将留在屏幕上,锁定你的UI。请参阅上面的示例使用代码,以避免在需要访问UITextField的情况下。

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

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

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

谢谢你,凯文!

你可以使用两种方法:

-使用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 ...];

这在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)

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

交叉发布我的回答,因为这两个线程没有被标记为dupes…

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

我在我的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;
}

除了给出的很棒的答案(agilityvision阿迪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

完整的例子:

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

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

似乎有效:

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

下面的解决方案确实工作,尽管它在所有版本中看起来都很有前途。这个解决方案正在生成WARNING

试图显示谁的视图不在窗口层次结构中!< / em >

< p > https://stackoverflow.com/a/34487871/2369867 = > 这在当时看起来很有希望。但它是Swift 3中的。 所以我在Swift 3中回答这个问题,这是模板示例。< / p >

一旦粘贴到任何函数中,这是相当完整的功能代码。

快速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中测试和工作的代码。

创建helper类AlertWindow并使用as

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

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

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

其中alertController是你的UIAlertController对象。

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

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

如果有人感兴趣,我创建了一个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
}
}
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),或者如果self is UIAlertController,则添加一个else if case以返回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
}
}
}

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

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转基因种子中仍然有效。然而,agityvision投票选出的最佳方法并没有:新创建的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种子中工作得很好,有正确的色调颜色,没有键盘滑动工件。

下面是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)
}


}

使用示例:

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

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

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

斯威夫特4 +

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

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


}

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

另一个选择:

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

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

其中一些答案只对我起了部分作用,将它们组合在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 ...

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
}


}


}

更新到与iOS 13场景,打破了新的UIWindow方法。斯威夫特5.1。

fileprivate var alertWindows = [UIAlertController:UIWindow]()


extension UIAlertController {


func presentInNewWindow(animated: Bool, completion: (() -> Void)?) {
let foregroundActiveScene = UIApplication.shared.connectedScenes.filter { $0.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
}


}

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

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

macOS上的Swift 5.5

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

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

斯威夫特5

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

查看类TopViewController:

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

我使用它很简单,像UIAlertController:

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

Swift 4+ / iOS 13:

基于https://stackoverflow.com/a/47797463/7493938,它使用了. keywindow方法,该方法已从iOS 13中弃用。

extension UIAlertController {
func show(animated: Bool = true, completion: (() -> Void)? = nil) {
if let visibleViewController = UIApplication.shared.windows.filter({$0.isKeyWindow}).first?.visibleViewController {
visibleViewController.present(self, animated: animated, completion: completion)
}
}
}

(我无法编辑原始答案,因为编辑队列已满。