I have a SplitViewVC that has a MasterNavVC who's root is a TableViewVC. The SplitViewVC also has a DetailNavVC that has a WhiteVC as it's root. I have several other view controllers that I want to get through from my TableViewVC: RedVC, GreenVC, BlueVC, and PinkVC. I didn't want to use all those IB segue connections so I want to push to them programmatically. The TableView's cell has a segue that pushes on the DetailNavVC thus all the other vcs have to go through it. I'm using this for iPad and iPhone adaptability.
The problem is in the TableView's didSelect method, when I try to push to any of the color vcs, the WhiteVC always shows pushing forward and popping when going backwards:
eg.
Push- TableView -> WhiteVC -> RedVC
Pop- RedVC -> WhiteVC -> TableView
I want
Push- TableView -> RedVC
Pop- RedVC -> TableView
I tried to remove the WhiteVC but I kept getting the exception:
Cannot display a nested UINavigationController with zero viewControllers
So I added the WhiteVC to silence that error but none of the methods below worked.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.row{
case 0:
//this shows the WhiteVC while pushing and popping
let redVC = storyboard?.instantiateViewController(withIdentifier: "RedVC") as! RedVC
navigationController?.pushViewController(redVC, animated: true)
break
case 1:
//this shows the WhiteVC while pushing but removes the backButton from the GreenVC
let greenVC = storyboard?.instantiateViewController(withIdentifier: GreenVC") as! GreenVC
navigationController?.setViewControllers([greenVC], animated: true)
break
case 2:
//this has the same effect as case 1
let blueVC = storyboard?.instantiateViewController(withIdentifier: BlueVC") as! BlueVC
let root = detailNavController(rootViewController: blueVC)
navigationController?.pushViewController(root, animated: true)
break
case 3:
//this shows the WhiteVC pushing but doesn't show it popping
let masterNav = splitViewController?.viewControllers.last as! MasterNavVC
let detailNav = masterNav.viewControllers.last as! DetailNavVC
let pinkVC = storyboard?.instantiateViewController(withIdentifier: "PinkVC") as! PinkVC
detailNav.setViewControllers([pinkVC], animated: true)
break
case 3 came the closet as the WhiteVC showed while pushing but it didn't show popping (it correctly popped to root):
Push- TableView -> WhiteVC -> PinkVC
Pop- PinkVC -> TableView
I want to programmatically push to the other color vcs (of course after tapping their selected cell) without showing the WhiteVC. How do I do that?
First thing thing let me say that the Apple recommends that the SpiltVC begin as root. The problem I ran into was that if using a TabBarVC as root you would have to put that inside a containerView and then make the containerView as root to a NavVC which would be root to the SplitVC.
SplitVC > NavVC > ContainerVC > TabBarVC //this wasn't working out
I decided to use a TabBarVC as root and added a separate SplitVC to each Tab. If you look on the left side of the pic below this is how Apple's MasterDetailApp looks when started up. The right side of the scene is the layout I instead used.
On the right side of the image I have a TabBar as root and each tab has a SplitVC and each SplitVc has it's own NavVC which itself has it's own TableVC as it's root:
____SplitVC -- NavVC -- TableVC //this would be tab 0 and where we will focus
/
/
TabBarVC //all the other color vcs I want to get to from the TableVC in tab 0
\
\____SplitVC -- NavVC -- TableVC
Notice on the right side of the image I didn't use include a DetailNavigationController like what's included in the MasterDetailApp.
I'm only going to focus on pushing vcs from the TabBar's first tab because you would use the same methodologies for the TabBar's second tab.
To begin in appDelegate's didFinishLaunching you would simply add the tab you want to land on first as the selectedIndex:
AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//I subclassed the TabBarVC with the name TabBarController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarVC: TabBarController = mainStoryboard.instantiateViewController(withIdentifier: "TabBarController") as! TabBarController
tabBarController.selectedIndex = 0
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
return true
}
FYI here is the flow and the name of the vcs for tab 0:
TabBarContoller > TabZeroSplitVC > TabZeroNavVC > SettingsVC
Launching this way would give me the SettingsVC which on an iPad would be on the left side (master side) in split screen mode. You also have to conform to the UISplitViewControllerDelegate and in viewDidLoad you make it so that it shows split screen with a master on the left and a detail on the right.
SettingsVC: UIViewController, UISplitViewControllerDelegate{
@IBOutlet weak fileprivate var tableView: UITableView!
var colors = ["RedVC", "GreenVC", "BlueVC", "PinkVC"]
override func viewDidLoad() {
super.viewDidLoad()
splitViewController?.delegate = self
//this keeps it in splitScreen mode
splitViewController?.preferredDisplayMode = UISplitViewControllerDisplayMode.allVisible
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.colors.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ColorsCell", for: indexPath) as! ColorsCell
cell.titleLabel?.text = self.colors[indexPath.row]
return cell
}
Since I didn't include a DetailNavigationController in Storyboard then the right side of the screen (detail side) would be blank. So upon launching you would get a screen that looks like this:
The idea is to first programmatically add a NavVC and then programmatically add the WhiteVC as it's root. That way the WhiteVC will initially show on the right side of the screen. The key is to use the splitVC's showDetailViewController(vc: UIViewController, sender: Any?)
to programmatically show it. Btw it is important to add the nav as a class variable because that is what we will use to show the other colors vcs.
SettingsVC: UIViewController, UISplitViewControllerDelegate{
@IBOutlet weak fileprivate var tableView: UITableView!
var colors = ["RedVC", "GreenVC", "BlueVC", "PinkVC"]
var whiteVC: WhiteController? //the one from the storyboard
var nav: UINavigationController? //this will represent the DetailNavigationController from Apple's MasterDetailApp
override func viewDidLoad() {
super.viewDidLoad()
splitViewController?.delegate = self
splitViewController?.preferredDisplayMode = UISplitViewControllerDisplayMode.allVisible
//1st instantiate the WhiteVC that was created in storyboard
self.whiteVC = storyboard?.instantiateViewController(withIdentifier: "WhiteController") as? WhiteController
//2nd add it to the programmatic navigationController as it's root
self.nav = UINavigationController(rootViewController: whiteVC!)
//3rd use the splitVC method to show the nav on the right side of scene
splitViewController?.showDetailViewController(self.nav!, sender: self
}
Now upon launch the scene will look like this:
Now to the answer the question of how to push on any of the color vcs without including the WhiteVC. All you have to do is add whichever color vc as root to the programmatic nav that was created as a class variable. And inside the tableView's didSelectRow
is where you add it and show from
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch indexPath.row{
case 0:
let redVC = storyboard?.instantiateViewController(withIdentifier: "RedController") as! RedController
self.nav = UINavigationController(rootViewController: redVC)
splitViewController?.showDetailViewController(self.nav! , sender: self)
break
case 1:
let greenVC = storyboard?.instantiateViewController(withIdentifier: "GreenController") as! GreenController
self.nav = UINavigationController(rootViewController: greenVC)
splitViewController?.showDetailViewController(self.nav! , sender: self)
break
case 2:
let blueVC = storyboard?.instantiateViewController(withIdentifier: "BlueController") as! BlueController
self.nav = UINavigationController(rootViewController: blueVC)
splitViewController?.showDetailViewController(self.nav! , sender: self)
break
case 3:
let pinkVC = storyboard?.instantiateViewController(withIdentifier: "PinkController") as! PinkController
self.nav = UINavigationController(rootViewController: PinkVC)
splitViewController?.showDetailViewController(self.nav! , sender: self)
break
}
Now if you picked the cell that is labeled RedVC you would get this (there should be a navigationBar on top of the RedVC but I forgot to add it in Photoshop):
If you look inside the didSelectRow you will see the nav now has a new root which is the redVC (it was originally using the WhiteVC in viewDidLoad). Since you changed the root the WhiteVC is no longer in the hierarchy. The same thing would follow for any of the other colors. If you choose the PinkVC you would get (there should be a navigationBar on top of the PinkVC but I forgot to add it in Photoshop):
In any case all you have to do is set a new root for the nav. If you wanted to add that double expand arrow thing that extends the view outwards
You would also add it in the didSelectRow
case 0:
let redVC = storyboard?.instantiateViewController(withIdentifier: "RedController") as! RedController
self.nav = UINavigationController(rootViewController: redVC)
//these 2 lines of code are what adds the double expand arrow
self.nav?.topViewController?.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
self.nav?.topViewController?.navigationItem.leftItemsSupplementBackButton = true
splitViewController?.showDetailViewController(self.nav! , sender: self)
break
//add the same exact 2 lines for every other case
One last thing. This was a big problem I ran into and I'm sure other people may run into this since this SplitVC isn't root. Let's say on tab zero upon launch you wanted to show another vc (i.e. an OrangeVC) instead of the SettingsVC. The problem is the setup is:
TabBarContoller > TabZeroSplitVC > TabZeroNavVC > SettingsVC
Since the storyboard has the SettingsVC as TabZeroNavVC's root, you would have to change it in the appDelegate's didFinishLaunching (or your login screen etc).
The code to use it would be:
AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//I subclassed the TabBarVC with the name TabBarController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarVC: TabBarController = mainStoryboard.instantiateViewController(withIdentifier: "TabBarController") as! TabBarController
tabBarController.selectedIndex = 0
//first you have to get to the splitVC on the first tab
let tabZeroSplitVC = tabBarController.viewControllers![0] as! TabZeroSplitController
//second you have to get to the navVC that's connected to the splitVC
let tabZeroNavVC = tabZeroSplitVC.childViewControllers[0] as! TabZeroNavController
//third instantiate the vc that you want to appear upon launch
let orangeVC = mainStoryboard.instantiateViewController(withIdentifier: "OrangeController") as! OrangeController
//the navVC has a method to set a new array of vcs. Just add the orangeVC in here (make sure to put it in array brackets)
tabZeroNavVC.setViewControllers( [orangeVC], animated: true)
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
return true
}
Once you launch your OrangeVC would show. Since the OrangeVC isn't a tableView you would probably want to show that full screen. Be sure to add the UISplitViewControllerDelegate and in viewDidLoad add:
OrangeVC: UIViewController, UISplitViewControllerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
splitViewController?.delegate = self
//this will hide splitScreen and will only show fullScreen
splitViewController?.preferredDisplayMode = UISplitViewControllerDisplayMode.primaryHidden
}
You would have a fullScreen of orange upon launch instead of having a splitScreen.
Even though these links use the SplitVC as root, these are some very good blog posts about configuring a SplitVC: