Show UIAlertController over keyboard

陌路散爱 提交于 2019-11-30 03:24:19
Leo Natan

I have implemented exactly this in our app. The trick is to have the alert controller appear on a different window. This is how the UIActionSheet implementation does it, and works great on iOS 8, but on 9, Apple has moved the keyboard implementation to a window which has a very high window level (10000000). The fix is to give your alert window an even higher window level (as a custom double value, not using the provided constants).

When using a custom window which will have transparency, make sure to read my answer here, regarding background color, to prevent window becoming black during rotation transitions.

_alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
_alertWindow.rootViewController = [UIViewController new];
_alertWindow.windowLevel = 10000001;
_alertWindow.hidden = NO;
_alertWindow.tintColor = [[UIWindow valueForKey:@"keyWindow"] tintColor];

__weak __typeof(self) weakSelf = self;

UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Test" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    weakSelf.alertWindow.hidden = YES;
    weakSelf.alertWindow = nil;
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Test" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    weakSelf.alertWindow.hidden = YES;
    weakSelf.alertWindow = nil;
}]];

[_alertWindow.rootViewController presentViewController:alert animated:YES completion:nil];

The answer supplied by Leo is broken as of iOS 11, because Apple now prevents you from setting a windowLevel above 10000000. A fix is to implement a custom UIWindow and override the windowLevel receiver:

@interface TopWindow : UIWindow @end

@implementation TopWindow
- (UIWindowLevel) windowLevel {
    return 20000000.000;
}
@end

// usage:
UIWindow* w = [[TopWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
w.rootViewController = [UIViewController new];
w.hidden = NO;

[w.rootViewController presentViewController:yourActionSheetController animated:YES completion:nil];

This approach should be backwards compatible, but haven't tested all known versions. Happy hacking!

Based on Leo Natan's answer, I've created a Swift extension for presenting an alert sheet over the keyboard.

In my brief testing, the alertWindow is deallocated after the alert is dismissed, I believe because there's no strong reference to it outside of the alert. This means there's no need to hide or deallocate it in your UIAlertActions.

extension UIAlertController {

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

        let alertWindow = UIWindow(frame: UIScreen.mainScreen().bounds)

        // If you need a white/hidden/other status bar, use an appropriate VC.
        // You may not need a custom class, and you can just use UIViewController()
        alertWindow.rootViewController = whiteStatusBarVC()

        alertWindow.windowLevel = 10000001
        alertWindow.hidden = false

        // Set to a tint if you'd like
        alertWindow.tintColor = UIColor.greenColor()

        alertWindow.rootViewController?.presentViewController(self, animated: animated, completion: completion)
    }
}

private class whiteStatusBarVC: UIViewController {
    private override func preferredStatusBarStyle() -> UIStatusBarStyle {
        return .LightContent
    }
}

use UIAlertController instead of UIActionSheet

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!