How to Present Uialertcontroller When Not in a View Controller

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 UIAlertController on top of everything regardless of the view hierarchy

Update Dec 16, 2019:

Just present the view controller/alert from the current top-most view controller. That will work :)

if #available(iOS 13.0, *) {
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.present(self, animated: true, completion: nil)
}

Update July 23, 2019:

IMPORTANT

Apparently the method below this technique stopped working in iOS 13.0 :(

I'll update once I find the time to investigate...

Old technique:

Here's a Swift (5) extension for it:

public extension UIAlertController {
func show() {
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindow.Level.alert + 1 // Swift 3-4: UIWindowLevelAlert + 1
win.makeKeyAndVisible()
vc.present(self, animated: true, completion: nil)
}
}

Just setup your UIAlertController, and then call:

alert.show()

No more bound by the View Controllers hierarchy!

Present UIAlertController in the currently active UIViewController

You can find the Top ViewController on the navigation stack and directly present the AlertController from there. You can use the extension method posted here to find the Top ViewController from anywhere in your application:

https://stackoverflow.com/a/30858591/2754727

Attempt to present UIAlertController whose view is not in the window hierarchy (Swift 3/Xcode 8)

First create UIAlertController such an attribute.

var alertController: UIAlertController?

And you must add this in the viewDidLoad() like this:

override func viewDidLoad() {
super.viewDidLoad()

self.alertController = UIAlertController(title: "Alert", message: "Not images yet", preferredStyle: .alert)
self.alertController?.addAction(UIAlertAction(title: "Close", style: .default))
view.addSubview((alertController?.view)!)

}

So when you press signInButton and login is incorrect you must invoke.

@IBAction func signInPressed(_ sender: Any) {

if usernameTextField.text == "" || passwordTextField.text == "" {

createAlert(title: "Error in form", message: "Please enter an email and password.")

} else {

var activityIndicator = UIActivityIndicatorView()

activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
activityIndicator.center = self.view.center
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()

PFUser.logInWithUsername(inBackground: usernameTextField.text!, password: passwordTextField.text!, block: { (user, error) in

activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()

if error != nil {

self.presentedViewController?.present(self.alertController!, animated: true, completion: nil)
}
}

Swift: Attempt to present UIAlertController whose view is not in the window hierarchy!

This line:

ViewController().present(alertView!, animated: true, completion: nil)

creates a new instance of ViewController and calls the present method on it. That won't work. You need to call it from a view controller that is itself presented. It looks like that code is inside ConsoleViewController, maybe you can just use self there.

Attempt to present UIAlertController on View Controller which is already presenting (null) [Swift]

The problem is really simple, you are trying to display another UIAlertController on the currently presented UIAlertController.

So, how to solve such a case?

  1. You need to get a list of all UIAlertController's you use in your current view controller.

  2. You have to check the logic for displaying alerts in your current view controller (or other view controllers if you are doing async requests).

  3. Your code must be like this when you want to display one alert on top of another.

Assume loadingAlert is currently displaying on the screen:

self.loadingAlert.dismiss(animated: true, completion: {
let anotherAlert = UIAlertController(title: "New One", message: "The Previous one is dismissed", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
anotherAlert.addAction(okAction)
self.present(anotherAlert, animated: true, completion: nil)
})

You have to dismiss the first one before the next one can appear. I made this answer for dismissing an alert without buttons on it to make it more efficient.

So, what about the alert with action buttons?

It will dismiss automatically when you click one of the action
buttons on UIAlertController that you created.

But, if you are displaying two UIAlertControllers which include UIButtons at the same time, the problem will still occur. You need to re-check the logic for each, or you can handle it in the handler for each action :

self.connectionErrorAlert.dismiss(animated: true, completion: {
let anotherAlert = UIAlertController(title: "New One", message: "The Previous one is dismissed", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: {action in
let nextAlert = UIAlertController(title: "New One", message: "The Previous one is dismissed", preferredStyle: .alert)
self.present(nextAlert, animated: true, completion: nil)
})
anotherAlert.addAction(okAction)
self.present(anotherAlert, animated: true, completion: nil)
})

For an Answer to Mike :

DispatchQueue.main.async(execute: {

if self.presentedViewController == nil {
print("Alert comes up with the intended ViewController")
var inputTextField = UITextField()

let textPrompt = UIAlertController(title: "Test", message: "Testing", preferredStyle: .alert)

textPrompt.addAction(UIAlertAction(title: "Continue", style: .default, handler: {
(action) -> Void in
// if the input matches the required text

let str = inputTextField.text
if str == requireTextInput {
print("right")
} else {
print("wrong")
}

}))

textPrompt.addTextField(configurationHandler: {(textField: UITextField!) in
textField.placeholder = ""
inputTextField = textField

})
weakSelf?.present(textPrompt, animated: true, completion: nil)
} else {
// either the Alert is already presented, or any other view controller
// is active (e.g. a PopOver)
// ...
let thePresentedVC : UIViewController? = self.presentedViewController as UIViewController?
if thePresentedVC != nil {
if let _ : UIAlertController = thePresentedVC as? UIAlertController {
print("Alert not necessary, already on the screen !")

} else {

print("Alert comes up via another presented VC, e.g. a PopOver")
}
}
}
})

Thanks to @Luke Answer : https://stackoverflow.com/a/30741496/3378606

show UIAlertController outside of ViewController

I wrote this extension over UIAlertController to bring back show().

It uses recursion to find the current top view controller:

extension UIAlertController {

func show() {
present(animated: true, completion: nil)
}

func present(animated: Bool, completion: (() -> Void)?) {
if let rootVC = UIApplication.shared.keyWindow?.rootViewController {
presentFromController(controller: 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(controller: visibleVC, animated: animated, completion: completion)
} else if
let tabVC = controller as? UITabBarController,
let selectedVC = tabVC.selectedViewController
{
presentFromController(controller: selectedVC, animated: animated, completion: completion)
} else if let presented = controller.presentedViewController {
presentFromController(controller: presented, animated: animated, completion: completion)
} else {
controller.present(self, animated: animated, completion: completion);
}
}
}

Now it's as easy as:

var alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .Alert)
alertController.show()

Warning: Attempt to present UIAlertController: whose view is not in the window hierarchy

Use this to present Alert.

func showResponseAlert(title:String?,message:String?){
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
topController.present(alert, animated: true, completion: nil)
}
}


Related Topics



Leave a reply



Submit