Show alert in AppDelegate in Swift [duplicate] - ios

This question already has answers here:
How to show UIAlertController from Appdelegate
(6 answers)
Closed 4 years ago.
I try the next code snippet:
var alert = UIAlertController(title: "Alert", message: "Cannot connect to : \(error!.localizedDescription)", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.Default, handler: nil))
self.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
in my AppDelegate, but it prints me the next error in console:
Warning: Attempt to present <UIAlertController: 0x7ff6cd827a30> on <Messenger.WelcomeController: 0x7ff6cb51c940> whose view is not in the window hierarchy!
How can I fix this error?

This is what i'm using now to do that.
var alertController = UIAlertController(title: "Title", message: "Any message", preferredStyle: .ActionSheet)
var okAction = UIAlertAction(title: "Yes", style: UIAlertActionStyle.Default) {
UIAlertAction in
NSLog("OK Pressed")
}
var cancelAction = UIAlertAction(title: "No", style: UIAlertActionStyle.Cancel) {
UIAlertAction in
NSLog("Cancel Pressed")
}
alertController.addAction(okAction)
alertController.addAction(cancelAction)
self.window?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

Swift 5:
let alert = UIAlertController(title: "Test", message:"Message", preferredStyle: UIAlertController.Style.alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
// show the alert
self.window?.rootViewController?.present(alert, animated: true, completion: nil)

As per Jorge's answer, updated for Swift 4
let alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
NSLog("OK Pressed")
}
let cancelAction = UIAlertAction(title: "CANCEL", style: UIAlertActionStyle.cancel) {
UIAlertAction in
NSLog("Cancel Pressed")
}
alertController.addAction(okAction)
alertController.addAction(cancelAction)
self.window?.rootViewController?.present(alertController, animated: true, completion: nil)

Swift 3.0 or above, Working in all condition , like in case of tab bar, in case of presented view etc ..
let alert = UIAlertController(title: "Test", message:"Message", preferredStyle: UIAlertControllerStyle.alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
// show alert
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alertController, animated: true, completion: nil)

I had the similar problem.
I have fixed it by presenting UIAlertController in Main Queue.
Code Looks like following.
let alert = UIAlertController(title: "My Title", message: "My Message", preferredStyle: .alert)
let actionYes = UIAlertAction(title: "Yes", style: .default, handler: { action in
print("action yes handler")
})
let actionCancel = UIAlertAction(title: "Cancel", style: .destructive, handler: { action in
print("action cancel handler")
})
alert.addAction(actionYes)
alert.addAction(actionCancel)
DispatchQueue.main.async {
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
}

Have you tried using UIApplication.shared.keyWindow?.rootViewController?.present(...) ?

I suppose you are calling that code snippet from the applicationDidFinishLunchingWithOptions.
I tried it as a matter of fact because I had to. The thing is: what you are trying to do is correct but the ViewController that the AppDelegate makes and presents is about to be put on screen and before that, the code snippet tries to create an alertView and put in on top of non existent View of the RootViewController.
What I would do is move it to another delegate call which is guaranteed to be called after the RootViewController is presented.
func applicationDidBecomeActive(application: UIApplication) {
//This method is called when the rootViewController is set and the view.
// And the View controller is ready to get touches or events.
var alert = UIAlertController(title: "Alert", message: "Cannot connect to :", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.Default, handler: nil))
self.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
But as always know the responsibility of the AppDelegate. It is to handle the application lifecycle and application wide delegate calls and events. If putting code here makes sense, then do it. But if you will be better off putting the code on the rootViewController or other parts then think about it too.
Anyway, hope it helps. Cheers!

I would suggest NOT doing this in the AppDelegate. The App Delegate it intended to handle Delegate functions from the OS rather than implementing things like alert views.
If you are wanting to present an alert view here to be shown at the start of the app I would do this by implementing it in your first view controller.

Related

how to present UIAlertController located in another UIViewController

I am getting the following error:
Attempt to present on Check5GHz: whose view is not
in the window hierarchy!
SplashViewController:
let check5ghz = Check5GHz()
check5ghz.determineIf5GHz()
Here is what Check5GHz looks like:
class Check5GHz: UIViewController {
func determineIf5GHz()-> Void{
let alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
So it seems like the problem is that one ViewController is trying to launch an UIAlertController found in another ViewController which it does not seem to allow. But I need to have this UIAlertController appear from many different ViewControllers. Could you recommend another way?
extension UIViewController {
func determineIf5GHz()-> Void{
let alert = UIAlertController(title: "Alert", message: "Message", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Click", style:
UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
Then in any of your view controllers do this:
self.determmineIf4GHz()

Trying to present 2 UIAlertControllers back to back

I have a UIAlertController in which the options are populated from an array and are presented to the user. The user then selects an option from the alert. After this, I have a separate alert that provides the user with a confirmation message that has an okay button.
myAlert.addAction(UIAlertAction.init(title: item, style: .Default, handler: {
(UIAlertAction) in
self.chosenBusiness.append(businessNameData[item]!)
}))
self.presentViewController(myAlert, animated: true, completion: nil)
The code above gathers the data from the array and pushes it into actions in myAlert. The code above is inside of a for loop.
After this I use a function to retrieve the topmost view controller, and then push the next alert.
let top = topMostController()
let alertController = UIAlertController(title: "Location pinned", message: "You've successfully pinned this location, good work!", preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) {
(result : UIAlertAction) -> Void in
print("OK")
}
alertController.addAction(okAction)
self.presentViewController(myAlert, animated: true, completion: nil)
top.presentViewController(alertController, animated: true, completion: {
_ in
})
The error I receive is:
Attempting to load the view of a view controller while it is
deallocating and is not allowed and may result in undefined behavior.
UIAlertController: 0x1535b1cd0.
Can someone help me with this?
I think this is what you are looking for. The second must be called with the dismissal action of the first. Also, anytime you work with UI, It is safer to use dispatch_async(dispatch_get_main_queue()) {
\\code }
than not if you are not positive you are currently on the main queue.
let firstAlertController = UIAlertController(title: "First", message: "This is the first message.", preferredStyle: UIAlertControllerStyle.Alert)
let secondAlertController = UIAlertController(title: "Second", message: "This is the second message.", preferredStyle: UIAlertControllerStyle.Alert)
let secondDismissAction = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, completion: nil)
secondAlertController.addAction(secondDismissAction)
let firstDismissAction = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
dispatch_async(dispatch_get_main_queue()) {
self.presentViewController(secondAlertController, animated: true, handler: nil)
}
}
firstAlertController.addAction(firstDismissAction)
self.presentViewController(firstAlertController, animated: true, completion: nil)

How do I access self from UIAlertAction in Swift?

I want to navigate back to root view controller when someone says ok to the alert action. But the Alert action does not allow to access self.
What is the work around to get current navigation controller in AlertAction,
here is the code,
func buttonAction(sender:UIButton!)
{
let alertController = UIAlertController(title: "IQ", message:"Thank you for your feedback!", preferredStyle: .Alert)
alertController.addAction(okAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
var okAction = UIAlertAction(title: "Menu", style:UIAlertActionStyle.Default) {
UIAlertAction in
NSLog("OK Pressed")
self.navigationController?.popToRootViewControllerAnimated(true) //error
}
All you have to do is to define a getter for the okAction property.
var okAction: UIAlertAction {
get {
return UIAlertAction(title: "Menu", style:UIAlertActionStyle.Default) {
UIAlertAction in
NSLog("OK Pressed")
self.navigationController?.popToRootViewControllerAnimated(true)
}
}
}
Tested in Xcode 7.1.1
You could do it this way instead:
let alert = UIAlertController(title: "Alert", message: "Message.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: { action in
self.navigationController?.popToRootViewControllerAnimated(true)
}))
self.presentViewController(alert, animated: true, completion: nil)
You need to dismiss the presented view controller (the Alert) before you try to pop from the navigation controller.

Create an UIAlertAction in Swift

I would like to create the following UIAlertAction:
#IBAction func buttonUpgrade(sender: AnyObject) {
let alertController = UIAlertController(title: "Title",
message: "Message",
preferredStyle: UIAlertControllerStyle.Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in
// ...
}
alertController.addAction(cancelAction)
}
I'm aware that an UIAlertController is initialized with a title, message, and whether it prefers to be displayed as an alert or action sheet.
When the button is pressed I would like to display the alert, but alert.show() doesn't work. Why doesn't my code work?
The main issue here is that UIAlertController (unlike UIAlertView) is a subclass of UIViewControlller, meaning it needs to be presented as such (and not via the show() method). Other than that, if you want to change to color of the cancel button to red, you have to set the cancel action's alert style to .Destructive.
This only works if you want the button to be red. If you want to change the colors of the buttons in the alert controller to arbitrary colors, this can only be done by setting the tintColor property on the alert controller's view property, which will change the tint color of all of its buttons (except those that are destructive). It should be noted that with the design paradigms that Apple has put in place, it isn't necessary to change the cancel button's color due to the implications of its having bolded text.
If you do still want the text to be red though, it can be done like this:
let alertController = UIAlertController(
title: "Title",
message: "Message",
preferredStyle: UIAlertControllerStyle.Alert
)
let cancelAction = UIAlertAction(
title: "Cancel",
style: UIAlertActionStyle.Destructive) { (action) in
// ...
}
let confirmAction = UIAlertAction(
title: "OK", style: UIAlertActionStyle.Default) { (action) in
// ...
}
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
presentViewController(alertController, animated: true, completion: nil)
Which produces the results you're after:
let alertController = UIAlertController(
title: "Title",
message: "Message",
preferredStyle: UIAlertControllerStyle.Alert
)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in
// ...
}
let okayAction = UIAlertAction(title: "OK", style: .Default) { (action) in
// ...
}
alertController.addAction(okayAction)
alertController.addAction(cancelAction)
self.presentViewController(alertController, animated: true) {
// ...
}
var alertController = UIAlertController(title: "Alert", message:"Message", preferredStyle: UIAlertControllerStyle.Alert)
let confirmed = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil)
let cancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)
alertController.addAction(confirmed)
alertController.addAction(cancel)
self.presentViewController(alertController, animated: true, completion: nil)
Important is the last line "self.presentViewController" to actually show your alert.
Hope it works ;)

How to create an alert in a subview class in Swift?

I have a view controller which contains a sub-view. And inside the sub-view class, I may need to pop out an alert when some condition is satisfied.
class GameViewController: UIViewController {
#IBOutlet var gameBoardUIView: GameBoardUIView
...
}
class GameBoardUIView: UIView {
...
func move() {
if !gameBoard.checkNextMoveExist() {
var alert = UIAlertController(title: "Game Over", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Take Me Back", style: UIAlertActionStyle.Cancel, handler: {(action: UIAlertAction!) in
println("Taking user back to the game without restarting")
}))
alert.addAction(UIAlertAction(title: "New Game", style: UIAlertActionStyle.Destructive, handler: {(action: UIAlertAction!) in
println("Starting a new game")
self.restartGame()
}))
// This is where the question is
// self.presentViewController(alert, animated: true, completion: nil)
}
}
}
As you can see from the code, I cannot call presentViewController function to show the alert because my sub view is not a controller class. Should I somehow create a week reference to the parent controller inside the sub-view? What would be the best practice to implement such reference?
Swift 4
Just present the alert in UIApplication windows in root view controller:
let title = "Some title"
let message = "body message"
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let action = UIAlertAction(title: "Aceptar", style: .cancel, handler: nil)
alert.addAction(action)
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
there are couple of ways how you can catch a UIViewController in your UIView.
you can make any of your view controllers as a delegate to show an alert;
you can pass a view controller's reference to your view; and
in general you can always grab the rootViewController anywhere in your code.
you need to call the dismissViewControllerAnimated(_: completion:) on the same view controller when you'd like to dismiss your alert later.
thus, I'd do such a quick solution for your case:
func move() {
if !gameBoard.checkNextMoveExist() {
let rootViewController: UIViewController = UIApplication.sharedApplication().windows[0].rootViewController
var alert = UIAlertController(title: "Game Over", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Take Me Back", style: UIAlertActionStyle.Cancel, handler: {(action: UIAlertAction!) in
rootViewController.dismissViewControllerAnimated(true, completion: nil)
println("Taking user back to the game without restarting")
}))
alert.addAction(UIAlertAction(title: "New Game", style: UIAlertActionStyle.Destructive, handler: {(action: UIAlertAction!) in
rootViewController.dismissViewControllerAnimated(true, completion: nil)
println("Starting a new game")
self.restartGame()
}))
rootViewController.presentViewController(alert, animated: true, completion: nil)
}
}

Resources