Why won't this Alert let my view controller deallocate? - ios

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.

Related

Why isn't reloadData() not working after deleting cells in collection view?

Basically Im pressing the delete button to delete all the cells and when I call the reloadData() it doesn't clear the cells. The only way the cells get cleared is when I leave the collection view screen and come back to it. Why does that happen?
#IBAction func deleteCell(_ sender: Any) {
//alert controller pops up
let alertController = UIAlertController(title: "Do you want to delete all images?", message: "", preferredStyle: .alert)
let action1 = UIAlertAction(title: "Delete", style: .default) { (action) in
self.collectionView.reloadData()
DataBaseHelper.instance.deleteAllRecords()
print("delete all")
}
//when pressed cancel alert sheet is dismissed and nothing happens
let action2 = UIAlertAction(title: "Cancel", style: .cancel) { (action) in
print("Cancel is pressed......remove alert sheet")
}
alertController.addAction(action1)
alertController.addAction(action2)
self.present(alertController, animated: true, completion: nil)
print("delete all cells")
}
The reloadData must be after the deleteAllRecords. But if you have swapped them and are still are having the problem, then either:
your delete process is running asynchronously (as is the case with many database APIs) and you’re not reloading when the asynchronous process is done; or
you are not updating your model (probably an array) once the records are deleted.

How to dismiss modal ViewController from UIAlertcontroller

I show a modal viewcontroller on which the user can decide to edit oder delete the presented car.
If the user wants to delete this car I present an UIAlertController with alert style to ask if he really wants to delete this car. Everything works fine. But after the user chooses "Yes" I am still in the modal viewcontroller.
How can I dismiss the modal view after the deletion?
I tried Following Code
self.parentViewController?.dismissViewControllerAnimated(true, completion: nil)
and
self.navigationController?.popViewControllerAnimated(true)
in the closure of the ok Action but both didn't worked for me. :(
There is no reason to write the code in
dispatch_async(dispatch_get_main_queue(), { //write your code here
})
The above code is used to the work in main thread. But here you are already on the main thread.
The issue here is you are calling
self.parentViewController?.dismissViewControllerAnimated(true, completion: nil)
Instead of it just write
self.dismissViewControllerAnimated(true, completion: nil)
Because you are presenting AlertController on self controller so the only one who can dismiss it is self
Put your code inside the
dispatch_async(dispatch_get_main_queue(), { //write your code here
})
like that :
func showDeleteWarningAndDeleteEntity() {
dispatch_async(dispatch_get_main_queue(), {
let deleteController = UIAlertController(title: "Delete car", message: "Are you sure?", preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) {
(action) in
}
let okACtion = UIAlertAction(title: "Yes", style: .Destructive) {
(action) in
//some network stuff happens here...
self.dismissViewControllerAnimated(true, completion: nil)
}
deleteController.addAction(okACtion)
deleteController.addAction(cancelAction)
self.presentViewController(deleteController, animated: true, completion: nil)
})
}
Hope this will works for you

NSNotification: Attempt to present UIAlertController on ViewController whose view is not in the window hierarchy

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)
}

Dismiss a View Controller with a UIAlertAction

I'm trying to present a log out alert. When the user taps on Yes, I want my view controller to dismiss with a method that can provide me a completion handler.
The view controller is inside a navigation controller and is the second one on the stack.
I came up with the following code:
#IBAction func logOut() {
let logOutAlert = UIAlertController.init(title: "Log out", message: "Are you sure ?", preferredStyle:.Alert)
logOutAlert.addAction(UIAlertAction.init(title: "Yes", style: .Default) { (UIAlertAction) -> Void in
//Present entry view ==> NOT EXECUTED
self.dismissViewControllerAnimated(true, completion:nil)
})
logOutAlert.addAction(UIAlertAction.init(title: "Cancel", style: .Cancel, handler: nil))
self.presentViewController(logOutAlert, animated: true, completion: nil)
}
The line self.dismissViewControllerAnimated(true, completion:nil) is read but it doesn't do anything.
I suspect that dismissViewControllerAnimated doesn't do anything for you because the view controller isn't presented modally, but shown by way of a navigation controller. To dismiss is, you can tell the navigation controller to pop it from the stack, like so:
logOutAlert.addAction(UIAlertAction.init(title: "Yes", style: .Default) { (UIAlertAction) -> Void in
self.navigationController?.popViewControllerAnimated(true)
})
Unfortunately, popViewControllerAnimated doesn't seem to provide a way to attach your own completion handler out of the box. If you require one, you could still add one by utilising the associated CATransaction, which could look something like this:
logOutAlert.addAction(UIAlertAction.init(title: "Yes", style: .Default) { (UIAlertAction) -> Void in
CATransaction.begin()
CATransaction.setCompletionBlock(/* YOUR BLOCK GOES HERE */)
self.navigationController?.popViewControllerAnimated(true)
CATransaction.commit()
})

Dismissal of UIAlertController (best practice)

When using UIAlertController like this:
var alert = UIAlertController(title: "Core Location",
message: "Location Services Disabled!",
preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default,
handler: nil))
self.navigationController.presentViewController(alert, animated: true,
completion: nil)
I noticed that the dismissal of the alert view is seemingly done automatically.
Shouldn't the dismissal of a presented ViewController be done by the presenting ViewController via a delegate call?
The dismissal is "included" in the presentViewController call. You do not need a delegate because you have the completion block. In this block you put what you would normally put into the delegate callback, except the call to dismiss the alert.
As far as "best practice" is concerned, I noted that in many APIs, Apple replaced delegate callbacks with completion blocks. Apple typically recommends using the block syntax. I surmise this could be partly because it helps keeping the related code sections together.
Is some Cases you may like to use this:
class MyAlertController : UIAlertController {
var dismissHandler : (()->())?
override func viewDidDisappear(_ animated: Bool) {
dismissHandler?()
super.viewDidDisappear(animated)
}
}
Usage:
let alert = MyAlertController(title: ...
let cancelButton = UIAlertAction(titl
alert.dismissHandler = { /*...do something */ }
alert.addAction(cancelButton)
...
There is an elegant way! Just write the action or function inside the alert controller's cancel action. (here the action style should be .cancel)
Code for Swift 3:
let Alert: UIAlertController = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)
let OkAction: UIAlertAction = UIAlertAction(title: “Ok”, style: .default) { ACTION in
//Will be called when tapping Ok
}
let CancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel) {ACTION in
// Will be called when cancel tapped or when alert dismissed.
// Write your action/function here if you want to do something after alert got dismissed”
}
Alert.addAction(OkAction)
Alert.addAction(CancelAction)
present(Alert, animated: true, completion: nil)

Resources