从 NSObject 推出 UIAlertController

引子

UIAlertView在iOS9之后被Apple废弃了,Apple推荐使用UIAlertController。但是,有时候我们使用UIAlertController时可能不是在一个UIViewController中,而是在一个NSObject中,但是UIAlertController是需要通过UIViewController才能present,那么如何在NSObject中正确使用UIViewController呢?
这里我总结了常用的三种方法。

方法一

创建一个带透明UIViewControllerUIWindow,在此上面推出UIAlertController。代码如下:

1
2
3
4
5
6
7
8
9
10
11
- (void)showAlert:(BOOL)animated {
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.rootViewController = [[UIViewController alloc] init];

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"title"
message:@"message"
preferredStyle:UIAlertControllerStyleAlert];

[window makeKeyAndVisible];
[window.rootViewController presentViewController:alertController animated:animated completion:nil];
}

方法二

创建一个UIAlertControllercategory,创建一个 show 方法来推出。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#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 insure window gets destroyed
self.alertWindow.hidden = YES;
self.alertWindow = nil;
}

@end

方法三

创建一个UIViewControllercategory,实现一个获取当前顶部可见的viewcontroller的方法,然后在上面present。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (UIViewController *)visibleViewControllerIfExist {

if (self.presentedViewController) {
return [self.presentedViewController visibleViewControllerIfExist];
}

if ([self isKindOfClass:[UINavigationController class]]) {
return [((UINavigationController *)self).topViewController visibleViewControllerIfExist];
}

if ([self isKindOfClass:[UITabBarController class]]) {
return [((UITabBarController *)self).selectedViewController visibleViewControllerIfExist];
}

if ([self isViewLoaded] && self.view.window) {
return self;
} else {
NSLog(@"找不到可见的viewController。self = %@, self.view.window = %@", self, self.view.window);
return nil;
}
}

+ (UIViewController *)visibleViewController {
UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
UIViewController *visibleViewController = [rootViewController visibleViewControllerIfExist];
return visibleViewController;
}

方法二来自于stackoverflow的问题How to present UIAlertController when not in a view controller?