I am not getting what's wrong with my code. I am simply displaying an alert with "Ok" button and when user click on "Ok", then alert should go. But its not getting disappeared. Using Swift3 for programming. Is viewDidAppear() right place to put this code? Or am I doing something wrong?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let alertController = UIAlertController(title: "Wrong Item", message: "Could not find details of an item.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
}
UPDATE:
When I put the same code in other controller, it worked.
In original controller, in viewDidLoad(), I have an Async call like below. Is this problem because of that?
DispatchQueue.global(qos: .background).async {
self.getDetails(onCompletion: {(json: JSON) in
let dict = self.convertToDictionary(text: json.stringValue)
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
//Other UI update operation
}
})
}
I also override viewWillDisappear() and viewWillAppear(), just to set Title of Screen.
are you calling UIApplication.shared.beginIgnoringInteractionEvents()
anywhere bro?? if yes that is your problem.
If we create a new "single view" project
For the the following two ways of presenting the alert we get the following behaviors
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let alertController = UIAlertController(title: "Wrong Item", message: "Could not find details of an item.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
}
In the console you will see
2017-08-15 16:27:35.871 test[66719:7754741] Warning: Attempt to present on whose view is not in the window hierarchy!
and no alert on the UI.
and for:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let alertController = UIAlertController(title: "Wrong Item", message: "Could not find details of an item.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
}
every thing works as expected.
So yes, view did appear is the place.
About viewDidLoad() and viewDidAppear(_:)
From the looks of it the problem lies with
beginIgnoringInteractionEvents
If you are putting your alert on viewDidAppear you should see it but if you don't see it, please note the following:
Even if you put this piece of code in viewdidload
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
//Other UI update operation
}
it may get executed later (depends on when the parsing finishes) than the call of viewDidAppear
and this is because:
DispatchQueue.global(qos: .background).async
Did you check this reason?
I add below code in viewDidAppear() just before Alert code and it started working. I kept below code at both location, i.e in Async call as well as in viewDidAppear()
self.activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
endIgnoringInteractionEvents() is in an async method when you click OK this method haven't been called. so you can't dismiss alert.
Your code looks fine, and viewDidAppear is fine as the your view controller will be loaded properly and it won't break the main thread to show you the alert. There's should be some other problem with your code, (bug of) Xcode, or derived data problem.
You can do a couple of things to see the actual problem:
Clean build
Delete file from Derived Data
Delete App from Simulator
Clean once again
Restart Xcode and Simulator
Rebuild to see if it works or not.
Related
I am trying to completely deallocate my view controller from memory. After hours of testing, I've finally narrowed it down to a UIAlertController staying in memory which keeps my view controller from deallocating.
#objc func logout_click() {
let alert = UIAlertController(title: "Confirmation", message: "Are you sure you want to log out?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "YES", style: .default, handler: { _ in
// 'YES' button action
do {
try Auth.auth().signOut()
self.popInit()
} catch {
print(error)
}
}))
alert.addAction(UIAlertAction(title: "NO", style: .default, handler: { _ in
// 'NO' button action
alert.dismiss(animated: true)
}))
self.present(alert, animated: true)
}
func popInit() {
//Go back to init screen
self.navigationController?.popToRootViewController(animated: true)
}
As long as this alert doesn't show, I can use popInit() and my view controller deallocates just fine, but after this alert shows up, even after dismissing, the view controller will not deallocate. I am not referencing any variables outside the scope of this function, so why does this not allow me to deallocate? What do I need to do to allow my view controller to deallocate?
Have the YES action handler declare [weak self] and call self?.popInit().
Also, as suggested in a comment, you can replace the NO handler with nil.
I'm new to Swift programming, but can't find an answer to my problem, which is...
When I present a simple UIAlertController with a UIAlertAction handler, I am expecting the alert to display until the user responds, then the handler is executed, before continuing with the remaining code.
Unexpectedly, it seems to finish off the code block before displaying the alert and executing the handler.
I've searched Stackoverflow, and re-read the Apple Developer Documentation for UIAlertController and UIAlertAction, but I can't figure out why the code doesn't pause until the user responds.
I've tried putting the UIAlertController code in its own function, but the alert still appears to be displaying out of sequence. I'm thinking maybe there needs to be a delay to allow the Alert to draw before the next line of code executes(?).
#IBAction func buttonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Ouch", message: "You didn't have to press me so hard!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Sorry", style: .default, handler: { _ in
self.handleAlert()
}))
self.present(alert, animated: true, completion: nil)
print("Should be printed last!")
}
func handleAlert() {
print("UIAlertAction handler printed me")
}
In the code above I am expecting the debug console to display:
UIAlertAction handler printed me
Should be printed last!
But instead it displays:
Should be printed last!
UIAlertAction handler printed me
Instead of adding a seperate function, can you put it within the alert action itself like this...
let alert = UIAlertController(title: "Ouch", message: "You didn't have to press me so hard!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Sorry", style: .default, handler: { action in
// code for action goes here
}))
self.present(alert, animated: true)
UIAlertController is designed to run asynchronously (that is why it has you pass a block of code to execute when the action is performed instead of giving a return value)
So to fix your code, put the code you want to run after an action is chosen in another function, then call that function at the end of each UIAlertAction handler.
private var currentlyShowingAlert = false
#IBAction func buttonTapped(_ sender: Any) {
if currentlyShowingAlert {
return
}
let alert = UIAlertController(title: "Ouch", message: "You didn't have to press me so hard!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Sorry", style: .default, handler: { _ in
self.handleAlert()
self.alertCleanup()
}))
self.present(alert, animated: true, completion: nil)
currentlyShowingAlert = true
}
func handleAlert() {
print("UIAlertAction handler printed me")
}
func alertCleanup() {
print("Should be printed last!")
currentlyShowingAlert = false
}
Be careful when doing things like pushing view controllers (or anything where the calls will stack up) in direct response to a button press.
When the main thread is busy, the button can be pressed multiple times before the first buttonTapped call happens, in that case buttonTapped could be called many times in a row, currentlyShowingAlert will prevent that issue.
I'm trying to show a UIAlertController in my ViewController in a function that's been called via an NSNotification. However I'm getting the error:
Attempt to present <UIAlertController: 0x7fe013d05d40> on <submarine.ViewController: 0x7fe011f20370> whose view is not in the window hierarchy!
The NSNotification is posted from a completion block (callback I guess) from something else in my UI. Because it's a callback it's failing to display. Hence I thought I'd try NSNotificationCentre to get around the problem without using the rootViewController to display the alert.
My code is:
override func viewDidAppear(animated: Bool) {
// Handle onboarding
if needsOnboarding() {
handleOnboarding() // This create the completion block that posts the NSNotification
}
NSNotificationCenter.defaultCenter().addObserver(self, selector: "showTermsAlert:", name:"showTermsAlert", object: nil)
}
func showTermsAlert(notification: NSNotification) {
let termsAlert:UIAlertController = UIAlertController(title: "Terms And Conditions", message: "Please view the terms below before accepting them.", preferredStyle: UIAlertControllerStyle.Alert)
termsAlert.addAction(UIAlertAction(title: "View Terms", style: .Default, handler: { (action: UIAlertAction!) in
UIApplication.sharedApplication().openURL(NSURL(string: "my_terms_url")!)
}))
termsAlert.addAction(UIAlertAction(title: "I Agree to the Terms", style: .Default, handler: { (action: UIAlertAction!) in
self.onboardingFinished()
}))
self.presentViewController(termsAlert, animated: true, completion: nil)
}
Has anyone got an idea why this is happening? I don't see why it's not in the window hierarchy - it's being presented from the self viewController and is created in a top-level function inside the VC.
Thanks!
EDIT: original code inside the handleOnboarding():
Library used: Onboard
func handleOnboarding() {
let secondPage = OnboardingContentViewController(title: "What's going on?", body: "Submarine routes your data through our network, around any filters and restrictions, giving you unrestricted and unmonitored internet access.", image: UIImage(named: "back"), buttonText: "Next") { () -> Void in
// do something here when users press the button, like ask for location services permissions, register for push notifications, connect to social media, or finish the onboarding process
}
secondPage.movesToNextViewController = true
let thirdPage = OnboardingContentViewController(title: "Terms of Use", body: "You must agree to our Terms of Use to use Submarine.\nIf you don't, please close Submarine.", image: UIImage(named: "back"), buttonText: "View Terms") { () -> Void in
let termsAlert:UIAlertController = UIAlertController(title: "Terms And Conditions", message: "Please view the terms below before accepting them.", preferredStyle: UIAlertControllerStyle.Alert)
termsAlert.addAction(UIAlertAction(title: "View Terms", style: .Default, handler: { (action: UIAlertAction!) in
UIApplication.sharedApplication().openURL(NSURL(string: "my_policy_url")!)
}))
termsAlert.addAction(UIAlertAction(title: "I Agree to the Terms", style: .Default, handler: { (action: UIAlertAction!) in
self.onboardingFinished()
}))
self.presentViewController(termsAlert, animated: true, completion: nil)
// NSNotificationCenter.defaultCenter().postNotificationName("showTermsAlert", object: nil)
}
// Image
let onboardingVC = OnboardingViewController(backgroundImage: UIImage(named: "back"), contents: [secondPage, thirdPage])
self.navigationController?.presentViewController(onboardingVC, animated: false, completion: nil)
}
This happen when the presenting view controller is no longer part of the controller hierarchy, and it's view is no longer in the view hierarchy of any window. Most likely, the controller was dismissed or popped, but it heard the notification and attempted to present the alert controller.
You should manage your controller states more carefully. Perhaps remove observer when the controller is dismissed or popped from your controller hierarchy.
There are a few things i'd change in your code. Add a call to super in viewDidAppear:, and stop using the NSNotifications for your presentation. You don't know what thread showTermsAlert will get called on with this pattern. You can make your intent more explicit by calling showTermsAlert directly, and this will also guarantee you're on the main thread.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Handle onboarding
if needsOnboarding() {
self.showTermsAlert()
}
}
func showTermsAlert() {
let termsAlert:UIAlertController = UIAlertController(title: "Terms And Conditions", message: "Please view the terms below before accepting them.", preferredStyle: UIAlertControllerStyle.Alert)
termsAlert.addAction(UIAlertAction(title: "View Terms", style: .Default, handler: { (action: UIAlertAction!) in
UIApplication.sharedApplication().openURL(NSURL(string: "my_terms_url")!)
}))
termsAlert.addAction(UIAlertAction(title: "I Agree to the Terms", style: .Default, handler: { (action: UIAlertAction!) in
self.onboardingFinished()
}))
self.presentViewController(termsAlert, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Initialize Alert View
let alertController = UIAlertController(title: "No Internet connection", message: "Please Connect to Internet", preferredStyle: .Alert)
let EXITAction = UIAlertAction(title: "EXIT", style: .Destructive) {(action) in
//...
exit(0)
}
alertController.addAction(EXITAction)
self.presentViewController(alertController, animated: true) {
// ...
}
}
I'm trying to use this for onLoad if use click then exit.
but nothing happen. Please help
(I am quite new in programming)
You have to move your code to present the UIAlertController to the viewDidAppear method.
If you put code that modifies the user interface in the viewDidLoad, it won't be applied to the views because the view controller is still loading.
Just declare a function like follows and you can call it in your viewDidAppear() method as alert("Message String").
func alert(info:String) {
let popUp = UIAlertController(title: "Title", message: info, preferredStyle: UIAlertControllerStyle.Alert)
popUp.addAction(UIAlertAction(title: "OK!", style: UIAlertActionStyle.Default, handler: {alertAction in
popUp.dismissViewControllerAnimated(true, completion: nil)
}))
self.presentViewController(popUp, animated: true, completion: nil)
}
I am working on my network connectivity,
I put checking reachability code in viewDidLoad, so that I notify User by alerting if there is not network.
Here is my code for this,
class ViewController: UIViewController {
var reachability : Reachability?
override func viewDidLoad() {
super.viewDidLoad()
if reachability?.isReachable() == true
{
print("reachable")
}else{
let myAlert = UIAlertController(title: "No network", message:
"Your network is not working", preferredStyle:
UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "Ok", style:
UIAlertActionStyle.Default, handler: nil)
myAlert.addAction(okAction)
self.presentViewController(myAlert, animated: true, completion:
nil)
}
but If I try on my simulator or cellphone,
it shows error message that
2015-11-08 16:43:52.173 PracticeReachability[5494:2661290] Warning: Attempt to present on whose view is not in the window hierarchy!
I tried put
var myAlert = UIAlertController()
and
var myAlert :UIAlertController!
both doesn't make it work.
Question
My other Alerts work fine in same ViewController.
Why only this is not working?
And How can I make it work?
I don't think the view controller's view has been added to the screen yet in viewDidLoad. Try moving your code to viewDidAppear.