Every Uialertcontroller Disappear Automatically Before User Responds - Since iOS 13

Every UIAlertController disappear automatically before user responds - since iOS 13

I've got exactly the same problem, and fixed it by holding the window in which the alert is being presented in a strong variable.

You can hold a window for presenting alerts in you AppDelegate, for example, and use it in your UIAlertController extension.

//In app delegate
let alertWindow: UIWindow = {
let win = UIWindow(frame: UIScreen.main.bounds)
win.windowLevel = UIWindow.Level.alert + 1
return win
}()

Then, in your extension:

public extension UIAlertController {
func show() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let vc = UIViewController()
vc.view.backgroundColor = .clear
vc.view.tintColor = Theme.mainAccentColor
appDelegate.alertWindow.rootViewController = vc
appDelegate.alertWindow.makeKeyAndVisible()
vc.present(self, animated: true, completion: nil)
}
}

You will also need to make sure your alert window is removed from view when your alert is dismissed, otherwise your app will become unresponsive, as all taps will be handled by the (invisible) alert window, that's still on top of everything.
I do this by adding this code to the handlers of all actions in the alert:

(UIApplication.shared.delegate as! AppDelegate).alertWindow.isHidden = true

UIAlertController disappearing since iOS 13

What seems to have changed is that on iOS 12 and previous versions your app would hold a strong reference to the window just by calling [alertWindow makeKeyAndVisible];, in iOS 13 it doesn't anymore.

What's happening is that the only strong reference to your alertWindow is in your requestHapticSetting func, and as soon as this func returns, the window is destroyed, thus removing your alert from the view.

This might be fixed just by adopting iOS 13 scenes, but I haven't tested that. What I can suggest, which won't properly work if you are using scenes, is holding your alert window in a strong variable somewhere in your code, and then using it to present the alert. I'd suggest doing so in a singleton or AppDelegate itself.

//AppDelegate.h
...
@property (strong) UIWindow *alertWindow;
....

//AppDelegate.m
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
self.alertWindow = [[UIWindow alloc] init];
self.alertWindow.rootViewController = [[UIViewController alloc] init];
self.alertWindow.windowLevel = UIWindowLevelAlert + 1;
...
}
...

//Your class that's presenting the alert
#import "AppDelegate.h"
...
- (void)requestHapticSetting{
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication;
[appDelegate.alertWindow makeKeyAndVisible];
if(isHapticOn){
hapticMessage = @"Haptic feedback is currently\nturned ON.\nPlease update preference.";
} else {
hapticMessage = @"Haptic feedback is currently\nturned OFF.\nPlease update preference.";
}

UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Haptic Setting"
message:hapticMessage
preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction* onAction = [UIAlertAction actionWithTitle:@"TURN ON" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self saveHapticSettingOn];
[appDelegate.alertWindow setHidden:YES];
}];

UIAlertAction* offAction = [UIAlertAction actionWithTitle:@"TURN OFF" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self saveHapticSettingOff];
[appDelegate.alertWindow setHidden:YES];
}];
[alert addAction:offAction];
[alert addAction:onAction];
[appDelegate.alertWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}

For Swift code, check this answer.

Present an UIAlertController on top of everything and stay on top even if another view is pushed (iOS 13)

You can use the same implementation as you used in iOS 12 and below, but you must hold a strong reference to the window you are presenting the alert on.

In your current code - when running on iOS 13 - the alertWindow will be destroyed as soon as showHighPriorityNotification finishes, dismissing your alert. It can be fixed by holding a strong reference to the alertWindow somewhere else.

Check this answer for a way of implementing that.

How to present UIAlertController when not in a view controller?

I posted a similar question a couple months ago and think I've finally solved the problem. Follow the link at the bottom of my post if you just want to see the code.

The solution is to use an additional UIWindow.

When you want to display your UIAlertController:

  1. Make your window the key and visible window (window.makeKeyAndVisible())
  2. Just use a plain UIViewController instance as the rootViewController of the new window. (window.rootViewController = UIViewController())
  3. Present your UIAlertController on your window's rootViewController

A couple things to note:

  • Your UIWindow must be strongly referenced. If it's not strongly referenced it will never appear (because it is released). I recommend using a property, but I've also had success with an associated object.
  • To ensure that the window appears above everything else (including system UIAlertControllers), I set the windowLevel. (window.windowLevel = UIWindowLevelAlert + 1)

Lastly, I have a completed implementation if you just want to look at that.

https://github.com/dbettermann/DBAlertController

Present UIAlertView on window

you have to add your UIViewController view in UIApplication keyWindow

extension UIViewController {
func presentControllerToWindow(){
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindow.Level.alert + 1
win.makeKeyAndVisible()
UIApplication.shared.keyWindow?.addSubview(vc.view) //added
vc.present(self, animated: true, completion: nil)
}
}


Related Topics



Leave a reply



Submit