Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Show UIAlertController over keyboard

In iOS 8 and lower show a UIActionSheet when keyboard is presented will present the action sheet over the keyboard. With iOS 9 this is no longer the case.

In my app we have a chat functionality and want the show a action over the keyboard. We used to use UIActionSheet which worked fine until iOS 8. In iOS 9 the action sheet is present behind the keyboard. I've tried both UIActionSheet and UIAlertController.

What we want is a action sheet like in messages.app Action sheet in messages app.

I've tried placing the action sheet in it own window and overriding canBecomeFirstResponder which just made the keyboard disappear.

like image 552
rckoenes Avatar asked Sep 18 '15 08:09

rckoenes


3 Answers

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

enter image description here

like image 197
Léo Natan Avatar answered Oct 21 '22 08:10

Léo Natan


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!

like image 8
Emiel Mols Avatar answered Oct 21 '22 07:10

Emiel Mols


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
    }
}
like image 5
Kyle Bashour Avatar answered Oct 21 '22 08:10

Kyle Bashour