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.
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:
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.
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.