user979331
user979331

Reputation: 11961

Swift 3 - Push segue to navigation controller

In my storyboard I have a View Controller, I also have a Navigation Controller and another View Controller called HistoryController. The Navigation Controller and the HistroyController have a relationship "root view controller"

I have a button on my 1st View Controller and that button has a push segue to the Navigation Controller.

I have this code in the 1st View Controller to to prepare the segue:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        if let viewController = segue.destination as? HistoryController {
            viewController.detailItem = barcodeInt as AnyObject
        }

    }

my problem is when I run my code and push the button in my first controller, I get this error:

Could not find a navigation controller for segue 'HistorySegue'. Push segues can only be used when the source controller is managed by an instance of UINavigationController.'

My question is, why am I getting this error and how can I fix it?

I have tried the following

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let navVC = segue.destinationViewController as? UINavigationController{
            if let historyVC = navVC.viewControllers[0] as? HistoryController{
                historyVC.detailItem = barcodeInt as AnyObject
            }
        }
    }

I have also tried

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let nav = segue.destination as? UINavigationController {
        if let vc = nav.visibleViewController as? HistoryController {
            vc.detailItem = barcodeInt as AnyObject
        }
    }
}

and I still get the same error:

Could not find a navigation controller for segue 'HistorySegue'. Push segues can only be used when the source controller is managed by an instance of UINavigationController.'

Upvotes: 9

Views: 16529

Answers (10)

Nitin Alabur
Nitin Alabur

Reputation: 5812

Have you tried checking the identifier also?

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if let nav = segue.destination as? UINavigationController where segue.identifier == "yourSegueIdentifierSetInTheStoryboard"{
    if let vc = nav.visibleViewController as? HistoryController {
        vc.detailItem = barcodeInt as AnyObject
    }
  }
}

Upvotes: 0

wj2061
wj2061

Reputation: 6885

You should use show segue instead of push segue.

The show segue will push the next viewController into the first viewcontroller's navigationController stack whenever possible, otherwise it will use modalPresent to show the next viewController.

If you embed your 1st viewController into a UINavigationController, and still keep your Navigation Controller,then you use push segue, you will get a exception:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Pushing a navigation controller is not supported'

Simple change your segue from push to show should fit in your case.

Upvotes: 1

Daniel
Daniel

Reputation: 377

Try this one:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let navVC = segue.destinationViewController as? UINavigationController{
        if let historyVC = navVC.childViewControllers[0] as? HistoryController{
            historyVC.detailItem = barcodeInt as AnyObject
        }
    }
}

Upvotes: 1

Antonio Anchondo
Antonio Anchondo

Reputation: 64

To use the push segue ViewController needs to be embedded in the NavigationController. If you don't want ViewController to be part of NavigationController's view controllers stack you could remove in HistoryController using:

override func awakeFromNib() {
    navigationController?.setViewControllers([self], animated: false)
}

An alternative is to have a segue from ViewController to NavigationController that is not a push segue, e.g. Present Modally or custom if you want to control the way the NavigationController shows in the screen.

Upvotes: 0

dirtydanee
dirtydanee

Reputation: 6151

The problem is that you are trying to push a segue using a UIViewController, not a UINavigationController.

Setup your storyboard the following way: enter image description here

As you might see, the first view controller is showing the segue to the second view controller, what is wrapped in a navigation controller.

This way, when you fire your segue, just use the following code to access your UIViewController subclass:

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let historyVC = segue.destination as? HistoryController {
      // do what you need to.
        }
 }

If you do not need a separate UINavigationController specifically to HistoryController, than you can just remove the one above HistoryController, and use the one attached to the first view controller.

Upvotes: 0

Mihai Fratu
Mihai Fratu

Reputation: 7663

In my storyboard I have a View Controller, I also have a Navigation Controller and another View Controller called HistoryController. The Navigation Controller and the HistroyController have a relationship "root view controller"

I have a button on my 1st View Controller and that button has a push segue to the Navigation Controller.

Now here's your problem. In your storyboard you should have a rootViewController relationship between the UINavigationController and FirstViewController and not to HistoryController. Then, the button in your FirstViewController should have a push segue connection to HistoryController. See the following picture:

enter image description here

Also, the app entry point should be the UINavigationController (just drag the little left sided arrow or select the UINavigationController and in the right side pannel check Is initial controller).

enter image description here

Upvotes: 0

Olivier Wilkinson
Olivier Wilkinson

Reputation: 2856

Your problem is not in your code, that is fine, it is in your storyboard.

If you would like to push to HistoryVC from ViewController, ViewController should be embedded in a UINavigationController, not HistoryVC (it would be embedded implicitly by it's relationship with ViewController).

The reason for this is that the VC that is pushing must be embedded in a UINavigationController, the vc that is being pushed is inherently embedded because it is added to the UINavigationController's stack.

enter image description here

If the initial ViewController is not within a UINavigationController, there is no stack for the pushed ViewController (HistoryVC) to be pushed onto. Basically, you embed your starting point for your stack (Your first ViewController) in the UINavigationController.

If you want to start a new stack a few VCs down the line you can embed the next view controller in a new UINavigationController, that however you would need to segue to modally.

Upvotes: 1

Armin Scheithauer
Armin Scheithauer

Reputation: 598

As the error says "you can't use a push segue if your source (1 st viewController) is not embedded in a navigationController.

You need to to change the type of your segue from "push" to "presentModally" - Normally that should work. You can still segue to the navigationController but not with a push - push only works inside of NavigationController.

Upvotes: 0

Geoherna
Geoherna

Reputation: 3574

You need to embed your source view controller in a Navigation Controller or change the segue kind from push to something else. Also, try and cast your destination controller to UINavigationController to prevent a different error after the initial one if fixed:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let navVC = segue.destinationViewController as? UINavigationController{
            if let historyVC = navVC.viewControllers[0] as? HistoryController{
                historyVC.detailItem = barcodeInt as AnyObject
            }
        }
    }

See if that works for you.

EDIT: Updated the code above.

Upvotes: 12

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let nav = segue.destination as? UINavigationController {
        if let vc = nav.visibleViewController as? HistoryController {
            vc.detailItem = barcodeInt as AnyObject
        }
    }
}

Upvotes: 0

Related Questions