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:
- Make your window the key and visible window (
window.makeKeyAndVisible()
) - Just use a plain UIViewController instance as the rootViewController of the new window. (
window.rootViewController = UIViewController()
) - 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?
You need to get a list of all
UIAlertController
's you use in your current view controller.You have to check the logic for displaying alerts in your current view controller (or other view controllers if you are doing async requests).
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 onUIAlertController
that you created.
But, if you are displaying two UIAlertController
s which include UIButton
s 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
How to Display 3 Cells Per Row in Uicollectionview
Looking to Understand the iOS Uiviewcontroller Lifecycle
Can You Animate a Height Change on a Uitableviewcell When Selected
Ios 9 Xcode 7 - Application Appears With Black Bars on Top and Bottom
Can You Attach a Uigesturerecognizer to Multiple Views
Change User Agent in Uiwebview
Disabling User Selection in Uiwebview
Nsoperation VS Grand Central Dispatch
Uicolor Not Working With Rgba Values
Iphone/Ipad: How to Make Uitextfield Readonly (But Not Disabled)
Iphone 6 and 6 Plus Media Queries
Send and Receive Messages Through Nsnotificationcenter in Objective-C
How to Make Http Request in Swift
What Is the Ibeacon Bluetooth Profile