1
votes

I am trying to build a storyboard in Xcode 7.x with more than 4 consequtive view controllers embedded in a UINavigationController. The navigation controller is the initial view controller. Embedded is a simple view controller only containing a single button, which is connected to an Adaptive Show Segue, calling a similar view controller and so on. Each one calling the next by a simple Show Segue. The Push only works for 3 consecutive calls, thereafter the reference to the navigation controller is lost. The 5th view controller and on are presented modally.

I am having this problem in different project at least since iOS 8.

Is the UINavigationController viewControllers stack limited to 4 entries?

More than 4 view controllers embedded in an UINavigationController

The code of the embedded view controllers:

class ViewController: UIViewController {

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        print("ViewCintroller:\(title) NavController:\(navigationController) stack size:\(navigationController?.viewControllers.count)")
        print()
    }
}

output:

ViewController:Optional("V1") NavController:Optional(<UINavigationController: 0x7fd57b024200>) stack size:Optional(1)
ViewController:Optional("V2") NavController:Optional(<UINavigationController: 0x7fd57b024200>) stack size:Optional(2)
ViewController:Optional("V3") NavController:Optional(<UINavigationController: 0x7fd57b024200>) stack size:Optional(3)
ViewController:Optional("V4") NavController:Optional(<UINavigationController: 0x7fd57b024200>) stack size:Optional(4)
ViewController:Optional("V5") NavController:nil stack size:nil
ViewController:Optional("V6") NavController:nil stack size:nil

changing all segues to the deprecated push type leads to the required result:

ViewController:Optional("V1") NavController:Optional(<UINavigationController: 0x7fc8a1026200>) stack size:Optional(1)
ViewController:Optional("V2") NavController:Optional(<UINavigationController: 0x7fc8a1026200>) stack size:Optional(2)
ViewController:Optional("V3") NavController:Optional(<UINavigationController: 0x7fc8a1026200>) stack size:Optional(3)
ViewController:Optional("V4") NavController:Optional(<UINavigationController: 0x7fc8a1026200>) stack size:Optional(4)
ViewController:Optional("V5") NavController:Optional(<UINavigationController: 0x7fc8a1026200>) stack size:Optional(5)
ViewController:Optional("V6") NavController:Optional(<UINavigationController: 0x7fc8a1026200>) stack size:Optional(6)

Edit:

In my opinion, the problem is with interface builder. I got a working solution with changing all show segues to push segues, compiling, changing the push segues back to show segues, compiling again and the anomaly is gone. The navigation stack isn't limited any more to 4 view controllers.

1

1 Answers

3
votes

Each one calling the next by a simple Show Segue. The Push only works for 3 consecutive calls, thereafter the reference to the navigation controller is lost. The 5th view controller and on are presented modally.

I've added an emphasis to this quote from your question.

A view controller which is presented modally will return nil for its navigationController property because it is not embedded in a navigation controller. If you continued presenting the 5th, 6th and so on with a Show seque rather than a modal, you'd continue to see that the navigation controller is non-nil and the navigation stack would continue to increase (until the device ran out of memory).

Besides available device memory, there is not a limit on the number of view controllers that can be added to a navigation controllers navigation stack.

Presenting a view controller modally does not add it to the navigation controller's navigation stack. A modally presented view controller will return nil for its navigationController property as it is not embedded in a navigation stack.


Consider the following example:

enter image description here

I have created this NextViewController class. The view controller just has the "Add Another" button, which is hooked up to the showAnother method. This method instantiates another NextViewController and pushes it onto the stack. In viewWillAppear, I am logging the same stuff you are logging in your prepareForSegue method.

I can continuously click "Add Another" as many times as I want, and I will continue to get another view controller added to the navigation stack, and you can see from the log messages, the number of view controllers in the stack is increasing well beyond four. (27 in the screenshot). This is consuming memory, and eventually I'll run out, yes.

Here's the plain-text code for the NextViewController class:

import UIKit

class NextViewController: UIViewController {
    init() {
        super.init(nibName: "NextViewController", bundle: NSBundle.mainBundle())
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        print("ViewCintroller:\(title) NavController:\(navigationController) stack size:\(navigationController?.viewControllers.count)")
    }

    @IBAction func showAnother() {
        navigationController?.pushViewController(NextViewController(), animated: true)
    }
}

For completion sake, I've added a second button which instantiates a new navigation controller with the NextViewController as the root view controller.

When I tap the blue button, the same thing happens as early. We add another NextViewController to the current navigation stack and the count increments. When we tap the orange one, we're looking at a new stack (notice the memory address of the navigation controller is now different) and the count resets to one.

enter image description here

Here's the code from that screenshot:

import UIKit

class NextViewController: UIViewController {
    init() {
        super.init(nibName: "NextViewController", bundle: NSBundle.mainBundle())
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        print("ViewCintroller:\(title) NavController:\(navigationController) stack size:\(navigationController?.viewControllers.count)")
    }

    @IBAction func showAnother() {
        navigationController?.pushViewController(NextViewController(), animated: true)
    }

    @IBAction func presentModally() {
        let anotherNavigationStack = UINavigationController(rootViewController: NextViewController())
        presentViewController(anotherNavigationStack, animated: true, completion: nil)
    }
}

Note here that the blue "Add Another" button is hooked up to showAnother() and the orange "Present Modally" button is hooked up to presentModally().

Here's a sample from the logs:

ViewCintroller:nil NavController:Optional(<UINavigationController: 0x7fa889810800>) stack size:Optional(8)
ViewCintroller:nil NavController:Optional(<UINavigationController: 0x7fa88c057a00>) stack size:Optional(1)
ViewCintroller:nil NavController:Optional(<UINavigationController: 0x7fa88c057a00>) stack size:Optional(2)
ViewCintroller:nil NavController:Optional(<UINavigationController: 0x7fa88c057a00>) stack size:Optional(3)
ViewCintroller:nil NavController:Optional(<UINavigationController: 0x7fa88c042800>) stack size:Optional(1)

Line one of these logs represents tapping the blue button for the 8th time consecutively. Line 2 represents having tapped the orange button. Notice that the memory address changes from 0x7fa889810800 to 0x7fa88c057a00. Lines 3 and 4 represent taps on the blue button. We notice that the memory address stays 0x7fa88c057a00, but the navigation count increments up to three (representing one orange tap followed by two blue taps). Then for line 5, we've tapped the orange button and the memory address changes again from 0x7fa88c057a00 to 0x7fa88c042800 and the count is reset to 1 again.