TIMEX
TIMEX

Reputation: 272264

How to correctly dismiss a UINavigationController that's presented as a modal?

In my TabBarViewController, I create a UINavigationController and present it as a modal.

var navController =  UINavigationController()
let messageVC = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController
self.presentViewController(self.navController, animated: false, completion: nil)
self.navController.pushViewController(messageVC, animated: false)

Inside my MessageViewController, this is how I want to dismiss it:

func swipedRightAndUserWantsToDismiss(){
    if self == self.navigationController?.viewControllers[0] {
        self.dismissViewControllerAnimated(true, completion: nil) //doesn't deinit
    }else{
        self.navigationController?.popViewControllerAnimated(true) //deinits correctly
    }
}

deinit{
    print("Deinit MessagesViewController")
}

The problem is that when I get to the root View Controller and try to dismiss both the child and the UINavigationController, my MessagesViewController deinit does not get called. Something's holding on to it -- most likely UINavigationController

Upvotes: 33

Views: 52612

Answers (8)

Faris
Faris

Reputation: 1236

You can use the following to correctly dismiss a UINavigationController that's presented as a modal in Swift 4:

self.navigationController?.popViewController(animated: true)

Upvotes: 9

Aruna Mudnoor
Aruna Mudnoor

Reputation: 4825

No need to have member for navController. Use following code to present your MessagesViewController.

let messageVC = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController
let pesentingNavigationController = UINavigationController(rootViewController: messageVC)
self.presentViewController(pesentingNavigationController, animated: true, completion: nil)

Your dismiss view controller code will be

func swipedRightAndUserWantsToDismiss() {
  self.navigationController.dismiss(animated: true, completion: nil)
}

Upvotes: 4

Max
Max

Reputation: 2299

if you want to just present a viewcontroller, then directly you can present that viewcontroller and no need to take a navigation controller for that particular viewcontroller.

But when we need to navigate from that presented view controller then we need to take a view controller as a root view of navigation controller. So that we can navigate from that presented view controller.

let messageVC = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController
let MynavController = UINavigationController(rootViewController: messageVC)
self.presentViewController(MynavController, animated: true, completion: nil)

and from that presented view controller, you can push to another view controller and also pop from another view controller.

And from presented view controller, here messageVC, we have to dismiss that as

func swipedRightAndUserWantsToDismiss() {
  self.dismiss(animated: true, completion: nil)
}

which will dismiss messageVC successfully and come back to origin viewcontroller from where we have presented messageVC.

This is the right flow to perform presentViewController with navigation controller, to continue the navigation between the view controllers.

And for more if you are not sure that messageVC is presented or pushed, then you can check it by this answer.

And the swift version to check that is

func isModal() -> Bool {
    if((self.presentingViewController) != nil) {
        return true
    }

    if(self.presentingViewController?.presentedViewController == self) {
        return true
    }

    if(self.navigationController?.presentingViewController?.presentedViewController == self.navigationController) {
        return true
    }

    if((self.tabBarController?.presentingViewController?.isKindOfClass(UITabBarController)) != nil) {
        return true
    }

    return false
}

So our final action to dismiss is like

func swipedRightAndUserWantsToDismiss() {

            if self.isModal() == true {
                self.dismiss(animated: true, completion: nil)
            }
            else {
                self.navigationController?.popViewControllerAnimated(true)
            }

        }

Upvotes: 5

Sulthan
Sulthan

Reputation: 130172

Your controller hierarchy looks like this:

UITabViewController
    |
    | presents
    |
UINavigationController
    |
    | contains view controllers
    |
[root, MessagesViewController]

Now, if you are inside MessagesViewController, then its navigationController is the one that is being presented and that's the one you should be dismissing but calling dismiss on MessagesViewController should work too.

However, the problem is that dismissing the navigation controller won't remove its view controllers. It seems you are holding to your navigation controller (since you are presenting it using self.navController) so the state will become

UITabViewController
    |
    | self.navController holds a reference to
    |
UINavigationController
    |
    | contains view controllers
    |
[root, MessagesViewController]

To properly destroy MessagesViewController you will have to either let go of the navController or you will have to pop to root (thus removing MessagesViewController from view hierarchy).

The typical solution would be not to save a reference to navController at all. You could always create a new UINavigationController when presenting. Another solution is using a delegate - instead of dismissing from inside MessagesViewController, let it call back to the presenter, which would call

self.navController.dismiss(animated: true) {
     self.navController = nil
}

Upvotes: 35

dannrob
dannrob

Reputation: 1069

In Swift 3 this is achieved with:

self.navigationController?.dismiss(animated: true, completion: nil)

Upvotes: 2

TechBee
TechBee

Reputation: 1941

This is how I solve the problem in Objective C.

You can call dismissViewControllerAnimated:NO on your self.navigationController itself.

Objective C

[self.navigationController dismissViewControllerAnimated:NO completion:nil];

Swift

self.navigationController.dismissViewControllerAnimated(false, completion: nil)

Upvotes: 2

Kelvin Lau
Kelvin Lau

Reputation: 6781

I suggest you use the other initializer for your UINavigationController:

let messageVC = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController
let navController = UINavigationController(rootViewController: messageVC)
self.presentViewController(self.navController, animated: true, completion: nil)

To dimiss, simply do

func swipedRightAndUserWantsToDismiss() {
  self.navigationController.dismissViewControllerAnimated(true, completion: nil)
}

Upvotes: 2

Shehzad Ali
Shehzad Ali

Reputation: 1843

Try this

func swipedRightAndUserWantsToDismiss(){
    self.navigationController.dismissViewControllerAnimated(false, completion:nil);
}

Upvotes: 12

Related Questions