iOS: UISplitViewController cannot be pushed to UINavigationController - ios

I have an XCode iPad project using a navigation controller. I tried to get a button to push a UISplitViewController to the navigation stack, but got this error:
Split View Controllers cannot be pushed to a Navigation Controller
Turns out UISplitViewController doesn't play nicely with UINavigationController. However, I still need to show the split view controller when this button is clicked. How do I do this? And, also important, how do I make a back button so the user can be returned to the navigation controller?

To display a SplitViewController you'll need to use setRootViewController. This is because a SplitViewController needs to be the root view controller.
From Apple's Documentation:
A split view controller must always be the root of any interface you
create. In other words, you must always install the view from a
UISplitViewController object as the root view of your application's
window. The panes of your split-view interface may then contain
navigation controllers, tab bar controllers, or any other type of view
controller you need to implement your interface.
To get back you'll need to use setRootViewController to go back to the earlier page. I ran into this problem when I converted my iPhone app to universal, and ended up using a navigation controller for the iPhone and setRootViewController for the iPad version. It's a bit of a bummer because you can't animate it nicely without a bit of fudging.

One workaround if you still need to navigate to splitView is to create an empty UIViewController and add the splitViewController as a child
/// This should be in your parent controller
/// that you to navigate your splitView
func navigateToSplit() {
let container = UIViewController()
let splitView = MySplitViewController() // ===> Your splitViewController
container.addAsChildViewController(type: splitView, attached: container.view)
navigationController?.pushViewController(container, animated: true)
}
extension UIViewController {
/// this add a child controller to the view of another controller
func addAsChildViewController(type controller: UIViewController, attached toView: UIView) {
// Add Child View Controller
addChild(controller)
// Add Child View as Subview
toView.addSubview(controller.view)
// Configure Child View
controller.view.frame = toView.bounds
controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
controller.didMove(toParent: self)
}
}

Pushing a split view controller through navigation controller is not
possible, but there is a alternative that get the job done
You can create a view controller and add the split view controller as a child and then you can push that created view controller through the navigation controller. It will show your split view controller and you can work with both master and detail.
*remember to add the yoursplitviewcontroller class to custom class in storyboard
note splitcontroller -> (master, detail)
let splitVC = getViewController(storyBoardName: "story board name", viewControllerName: "split view controller identifier") as! yoursplitviewcontroller
view.addSubview(splitVC.view)
view.bounds = splitVC.view.bounds
addChild(splitVC)
func getViewController(storyBoardName: String, viewControllerName: String) -> UIViewController{
let storyBoard = UIStoryboard(name: storyBoardName, bundle: nil)
return storyBoard.instantiateViewController(identifier: viewControllerName)
}

I added a 6th tab containing a SplitView, to my application. On iPad the new tab worked fine when selected, but on iPhone the new tab was moved to the "More…" tab and when selected produced the "Split View Controllers cannot be pushed to a Navigation Controller <UIMoreNavigationController:" message.
I solved the problem by moving the new tab so that it could not fall into the "More…" tab.
A more in depth discussion of the problem can be found at:
Tab Bar Controller with seven tabs, Five tabs lead to Split View Controllers

Related

Going to a navigation controller inside a Tab Bar

This is how my storyboard is laid out:
The first View controller on the left is the initial VC and this decides which VC to go to if you are signed in or not. I have not decided to implement a Tab Bar controller to have a tab bar. However I need a navigation controller going to each VC coming out of the tab bar.
If I use this code to navigate over, it shows a black screen (It worked fine before adding a Tab Bar Controller):
let mainNavController = storyboard?.instantiateViewController(identifier: Constants.Storyboard.mainNavController)
view.window?.rootViewController = mainNavController
view.window?.makeKeyAndVisible()
If I use this code, all of the segues show modally:
let viewController = storyboard?.instantiateViewController(identifier: Constants.Storyboard.homeViewController) as? HomeViewController
view.window?.rootViewController = viewController
view.window?.makeKeyAndVisible()
And as s last resort I tried to go straight to the Tab Bar controller but then I got this error:
Storyboard (<UIStoryboard: 0x600000e88840>) doesn't contain a view controller with identifier 'tabBarController'
So my problem is I want to navigate from a View controller programatically to the show the Tabbed VCs and use a navigation controller so all of the segued view controllers don't show modally.
(I have tried using individual Navigation Controllers for each of the tabbed vCs but that shows a black screen also)
This was the original with the tabbed VCs having individual Navigation Controllers:
And the error I got when trying to programatically go to that was
Storyboard (<UIStoryboard: 0x600000e88840>) doesn't contain a view controller with identifier 'tabBarController'
Even though it does

How to hide Tabbarcontroller's first view controller and go directly to the next controller but should show the tab bar items at the bottom

I have a view controller as my initial view controller. there's a button in it(GO button) which when the user taps, it should go to another view controller(let's call it Destination view controller with label 'This is where i wanna go'). Meanwhile i want to pass it through a Tabbar controller. The reason is i want to have tabbar in my navigation stack.
I wish to go directly to the Destination view controller while pressing go button but it should show the tab bar items at the bottom.
So for achieving this in FirstViewController didLoadMethod I checked a bool value and pushed the view controller to the Destination view controller. I achieved the result I.e when pressing the Go button it goes to the Destination view controller and has tab bar items at it's bottom.
But the problem since it passes through the Tabbarcontroller the FirstViewController is shown for some seconds and then it pushes to the Destination view controller. I wish to hide FirstViewController while this transition takes place.
How to achieve this?
Picture shows what i want. what can I do to hide firstviewcontroller while having it in navigation stack?
I think this can be done in a simple way -
In the first viewController of the tab bar has a viewDidLoad() function or you can use loadView() which is called before the viewDidLoad() function. Push to the next viewController in the function.
Put your push navigation code in one of those functions
*You cant see the current view coltroller, it will push the screen to your required viewcontroller before loading the tab bar initial view contoller.
Hope it will work for you.
or >>>> you can check it out
let storyboard = UIStoryboard(name: "your_storyBoard_name", bundle: nil)
let viewController1 = storyboard.instantiateViewController(withIdentifier: "firstViewController")
let viewController2 = storyboard.instantiateViewController(withIdentifier: "secondViewcontroller")
let controllers = [viewController1, viewController2]
self.navigationController!.setViewControllers(self.navigationController!.viewControllers + controllers, animated: true)
The effect you're trying to produce is hard to do in a storyboard. Programmatically you would simply create the Tabbar Controller (with its children) and the "This is where I want to go" Controller, and then ask the navigation controller to show both at the same time.
For example, after "Go" is tapped, this is the code I would run inside your first view controller:
let tabBarController = UITabBarController()
let finalDestination = UIViewController()
var viewControllers = self.navigationController?.viewControllers ?? []
viewControllers.append(tabBarController)
viewControllers.append(finalDestination)
self.navigationController?.setViewControllers(viewControllers, animated: true)
Given the structure you shown, where the view controller A is the root view controller of the TabBar, you should push the second view controller B on the navigation stack inside either willAppear or didLoad of view controller A, according to your personal business logic (flag, conditions, etc.).
The trick here is to use either pushViewController or setViewControllers with animated: false so that the navigation stack will be set immediately during willAppear/didLoad and it won't show the push animation of B over A. This way, at onDidAppear the layout will be already fully rendered in it's final state: with B at the top of the navigation stack and no animations in progress.

How do I segue to a scene a few layers into a navigation controller stack?

I had to add a navigation controller to my app so that I could use the left drawer menu (using SWRevealViewController) but its messing up my segues. My initial design had a login screen that segued to one of 4 different scenes depending on an a status indicator.
Now that I had to add a navigation controller it looks like i'll have to take the user sequentially through through each screen in the stack until they reach the relevant one. Is there a way I can jump past the first screen or 2? Or a way to not show them as I navigate through.
I tried putting the performSegue in the viewWillLoad delegate method but the screen still loads before segueing to the next scene.
Add Storyboard IDs to all View/Navigation Controllers that will eventually be pushed:
Now to push the desired view it depends whether your current View Controller stands: within or outside the Navigation Controller's stack:
If your VC is already in the Navigation Controller stack
From your current ViewController push the desired view:
if let myViewController = self.storyboard?.instantiateViewControllerWithIdentifier("myViewController") as? MyViewControllerClassName {
self.navigationController?.pushViewController(myViewController, animated: true)
}
Note: as? MyViewControllerClassName is only required if your View Controller's class is not the default UIViewController but a custom one that extends it instead.
If your VC is NOT in the Navigation Controller stack
Same principle apply, only this time you need to push the Navigation Controller itself before pushing the desired View Controller:
if let newNavController = self.storyboard?.instantiateViewControllerWithIdentifier("myNavigationController") as? UINavigationController {
self.view.window?.rootViewController = newNavController
// Now push the desired VC as the example above, only this time your reference to your nav controller is different
if let myViewController = self.storyboard?.instantiateViewControllerWithIdentifier("myViewController") as? MyViewControllerClassName {
newNavController.pushViewController(myViewController, animated: true)
}
}

Detail UINavigationController issue in UISplitViewController

I have current UISplitViewController setup:
UISplitViewController with master UINavigationController that contains UITableViewController and detail controller that contains UITabBarController.
Code:
// Create split view controller
let splitViewController = UISplitViewController()
let masterViewController = UINavigationController(rootViewController: UITableViewController())
masterViewController.topViewController?.title = "Master"
// Create tab bar controller
let tabBarController = UITabBarController()
// Setup view controllers
var viewControllers = [UIViewController]()
for i in Range(start: 0, end: 4) {
let vc = UIViewController()
vc.view.backgroundColor = UIColor.whiteColor()
vc.title = "View Controller \(i+1)"
let navigationController = UINavigationController(rootViewController: vc)
viewControllers.append(navigationController)
}
tabBarController.viewControllers = viewControllers
splitViewController.viewControllers = [masterViewController, tabBarController]
That yields following on iPhone 6S Plus in landscape:
Issue:
After rotation to the portrait mode, UINavigationController from detail view controller is replaced with master UINavigationController, instead of using navigation controller from the detail view controller.
It's obviously the expected behavior, but I would like to use UINavigationController from the detail view controller and still have a back button for the master view controller. You can look at the Facebook Messenger app to see what am I talking about.
The issue has to do with the fact that you're trying to embed a tab bar controller in a navigation controller, and make that the detail view controller for a split view controller.
The tab bar controller would expect to change navigation items for the selected tab, but your hierarchy is opposite of what it expects.
While the tab bar controller is able to display a selected tab's navigation items on a detail navigation controller which isn't collapsed, things break down once collapsed as the tab bar controller doesn't realize that it's been pushed onto a view controller stack of the master navigation controller. It's updating the wrong navigation bar at that point.
The SDK doesn't natively support that particular Adaptive UI hierarchy. You could file a feature request, or see if another developer has code to work around how the split view controller delegate would need to collapse an embedded tab bar controller.

PresentViewController to a ViewController with a different NavigationController but on the same storyboard

I'm having trouble with a simple Xamarin Studio Storyboards concept. See screenshots below for visuals and see the downloadable source code here.
Let's say I have a NavigationController with MainViewController on it. This is visible in my storyboard. I want a button which, when pressed, brings up a new NavigationController with RedViewController. I also want RedViewController on the same storyboard as the MainViewController. In this project, I tried to do that but for some reason when I do a:
var myStoryboard = AppDelegate.Storyboard;
// Instatiating View Controller with Storyboard ID 'StuffViewController'
RedViewController = myStoryboard.InstantiateViewController ("RedViewController") as RedViewController;
RedViewController.ModalTransitionStyle = UIModalTransitionStyle.CoverVertical;
this.PresentViewController(RedViewController, true, null);
the RedViewController doesn't have it's Navigation controller with it. When presented RedViewController's Navigation Controller is null! What am I doing wrong there?
Now when I created a NavigationController & BlueViewController in a totally seperate storyboard it works fine. When I press the Blue Button it goes to the BlueViewController and correctly shows it's NavigationController. Why is this one working but the other one not? The only difference that I can see is that they are on separate Storyboards.
UIStoryboard storyBoard = UIStoryboard.FromName ("BlueStoryboard", null);
UIViewController controller = storyBoard.InstantiateInitialViewController () as UIViewController;
controller.ModalTransitionStyle = UIModalTransitionStyle.CoverVertical;
this.PresentViewController (controller, true, null);
ViewController that can present a new NavigationController & ViewController ViewController called "Red" with a navigation bar
When you instantiate your new view controller you need to instantiate the UINavigationController, not the RedViewController.
In the case of your 'blue' code you instantiate the initialViewController - which is the navigation controller that contains the Blue controller.
You want
RedViewNavigationController = myStoryboard.InstantiateViewController ("RedViewNavigationController") as UINavigationController;
where 'RedViewNavigationController' is the identifier for the navigation controller that the Red View Controller is embedded in.
If you want to present the red controller with its navigation controller, you should instantiate the navigation controller (which, in turn, will instantiate the red controller), and present it.

Resources