UIAlertController is complete and presented but does not appear during viewDidLoad - swift

I have this alert controller setup to appear as soon as the view controller is loaded. However, it does not appear.
I believe I have all the facets covered - title, message, alert style, action button and present... but still does not appear.
Unsure what I'm missing.
let array = quoteBank()
print(array.sarcasticQuotes[0].quote)
let title = "Message"
let message = array.sarcasticQuotes[0].quote
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(.init(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)

Attempting to show an alert in viewDidLoad is too soon. The view controller isn't displayed yet. Use viewDidAppear.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Use this if statement to only show the alert once
if self.isBeingPresented || self.isMovingToParentViewController {
// show your alert here
}
}

You need to perform that on the main thread:
DispatchQueue.main.async {
present(alert, animated: true, completion: nil)
}
The reason behind that is that the view controller's hierarchy will be set after viewDidLoad is finished. So by doing this, you're scheduling the presentation of the alert on the main thread to the time the main thread is finished from executing viewDidLoad.

Related

UIAlertController dismiss completion never gets called

In the following code, when my web view fails to load, the alert is properly shown with the Retry button, as expected. The alert goes away when tapping the Retry button, but the completion never gets called. Why is this?
func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
let alert = UIAlertController(title: "Network Error", message: "There was a error loading the page.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Retry", style: .default, handler: { _ in
alert.dismiss(animated: true, completion: {
self.webView.loadHTMLString("Reloaded", baseURL: nil)
})
}));
self.present(alert, animated: true, completion: nil)
}
Don't call alert.dismiss inside the alert action. The alert controller will automatically be dismissed when the user taps one of the alert buttons.
You just need:
alert.addAction(UIAlertAction(title: "Retry", style: .default, handler: { _ in
self.webView.loadHTMLString("Reloaded", baseURL: nil)
}))
Just try it out I didn't checked ,
Change UIAlertActionStyle .default to .Cancel. Remove dismiss block give web view loading directly in handler..
Hope maybe it will help you...

Action sheet does not work in iPhone simulator

I am trying to implement the Action sheet in Swift. Below is the code to implement it. When i execute the code, Xcode does not show any errors and the action sheet does not appear in the simulator. Any help in resolving the issue is much appreciated.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
func showActionSheet(sender: AnyObject) {
let actionSheetController: UIAlertController = UIAlertController(title: "Action Sheet", message: "Choose an option!", preferredStyle: .actionSheet)
//Create and add the Cancel action
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel) { action -> Void in
//Just dismiss the action sheet
}
actionSheetController.addAction(cancelAction)
self.present(actionSheetController, animated: true, completion: nil)
The way your code is provided, it looks as if you added a method/function inside viewWillAppear. If that is not a typing mistake and you did indeed set your code up like that, modifying your code to look like this will get it to work:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let actionSheetController: UIAlertController = UIAlertController(title: "Action Sheet", message: "Choose an option!", preferredStyle: .actionSheet)
//Create and add the Cancel action
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel) { action -> Void in
//Just dismiss the action sheet
}
actionSheetController.addAction(cancelAction)
self.present(actionSheetController, animated: true, completion: nil)
}
Basically, do not have a method inside viewWillAppear but also move your action sheet display code to viewDidAppear instead of viewWillAppear

How To Make an Alert Only Appear Once

I'm trying to figure out is how to create a pop up window that only appears once when you start the app and then won't appear again unless you close the app and reboot it. However, if you view the code below, you will realize that the alert will pop up every time the ViewController appears. For example, if you go to the settings tab and then return to the main ViewController, then the alert will pop up.
override func viewDidAppear(animated: Bool) {
let alertController = UIAlertController(title: "Disclaimer", message: "WARNING: Please ride carefully!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Accept", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
Just create a global variable that is a Bool. If the app is opened it starts at false. Then once the disclaimer is seen it sets the variable to true. Then present the View Controller based on the value of the variable.
var disclaimerHasBeenDisplayed = false
class ViewController {
override func viewDidAppear(animated: Bool) {
if disclaimerHasBeenDisplayed == false {
disclaimerHasBeenDisplayed = true
let alertController = UIAlertController(title: "Disclaimer", message: "WARNING: Wakeboarding is fun, however it can be highly dangerous.
Wake Dice is not liable for any injuries obtained while wakeboarding. Please ride carefully!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Accept", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
}
}
}

How to show one UIAlertController after the other in Swift

I want to show user a message once.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let defaultsDatafirstTrue = NSUserDefaults.standardUserDefaults()
if let _ = defaultsDatafirstTrue.stringForKey("firstTrue") {
} else {
let alertController = UIAlertController(title: "You can add road markers just do long press on the Map", message: "", preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action:UIAlertAction) in
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true, completion:nil)
defaultsDatafirstTrue.setObject("true", forKey: "firstTrue")
}
}
But I have error Warning: Attempt to present <UIAlertController: 0x7c2a8000> on <****: 0x7d193800> whose view is not in the window hierarchy! Because at the first run the iOS app displays a warning to the user that will use the location determination.
How can I see my message after the system message?
Run the code from viewDidAppear: not from viewWillAppear:.
The problem with viewWillAppear: is that it is called before the view is actually visible, so you cannot present another view from it yet. Also, viewWillAppear: can be actually called multiple times and then cancelled (e.g. for interactive transitions).

Attempt to present ViewController which is already presenting (null)

I am creating a user and logging in the user after using the Facebook SDK. All is as it should be with correct inserting the user into the database. I then want to present an alert and upon dismissal of the alert, I want to load a different storyboard (essentially the main menu).
Now my understanding is the error is an issue with threading. BUT.. I am passing the loading of the storyboard etc in as a block to the alert. So shouldn't it then get called only after the user dismisses the alert? I also tried making it run on the main thread (which was mentioned here) but I am still getting the error.
What am I missing here?
The originating call:
if DBUser.insertDictionary(user.getModelAsStringDictionary()) {
if DBUser.loginUserWithFacebookId(facebookId!){
self.accountCreatedAlert()
}else{
//log error, user could not be logged in.
}
}else{
// log error user account could not be inserted into the database
}
}
The async bundling of the alert text and the switchToMainStoryboard() method:
private func accountCreatedAlert(fn:()){
let asyncMoveToMain = {dispatch_async(dispatch_get_main_queue()) {
self.switchToMainStoryboard()
}}
self.showAlert("You will now be logged in", title: "Account created", fn: asyncMoveToMain)
}
The Alert:
func showAlert(text : NSString, title : NSString, fn:()->Void){
var alert = UIAlertController(title: title, message: text, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
UIApplication.sharedApplication().delegate?.window!?.rootViewController?.presentViewController(alert, animated: true, completion: fn)
}
And where the storyboard gets loaded:
func switchToMainStoryboard() ->Void{
let main = UIStoryboard(name: "Main", bundle: nil)
var viewController = main.instantiateInitialViewController() as UIViewController
viewController.modalTransitionStyle = UIModalTransitionStyle.CoverVertical
self.presentViewController(viewController, animated: true, completion: nil)
}
So I tried a lot of messing about with the dispatch_async and dispatch_sync methods and each time, no matter what I did I was getting a lock condition that froze the UI.
Upon further inspection of the documentation (and my code), I provided the block instead to the AlertControllerAction instead of the presentViewController. Thanks to this post for providing the syntax. The resulting changes are as follows:
// MARK: Alert Related Methods
func showAlert(text : NSString, title : NSString, fn:()->Void){
var alert = UIAlertController(title: title, message: text, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: {(alert: UIAlertAction!) in fn()}))
UIApplication.sharedApplication().delegate?.window!?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
And then went about calling it like so:
private func showAccountCreatedAlertThenLoadMainStoryboard(){
self.showAlert("You will now be logged in", title: "Account created", fn: {self.switchToMainStoryboard()})
}
The required sequence is now happening without issue. Hope this helps someone else.
Alternative way is that you can ask GCD to delay with your next presentation, like so:
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) {
// next present
}