I have an app which has a login screen and when the user logs in, a tab bar controller is pushed. I currently have some views that would benefit from the fact that apple now allows using the split view controller in all iOS devices, so I was preparing to implement this when I read that the UISplitViewController must always be the root view controller. So I was wondering if it is possible to make the view in one of the tabs become a master-detail view using a UISplitViewController or will I need to implement this manually?
In case it is not possible to show the split view as a tab, could it be pushed from the tab bar controller? (e.g. the user taps a row in a table view and the master-detail view appears).
UISplitViewController in iOS 14 gained new API including a new column style that behaves differently from the unspecified style which is the "classic" interface. Using the modern column-style API, if you try to embed a UISplitViewController in a UITabBarController, it may not behave as you'd expect. For example, at least as of iOS 15, only the secondary view controller may be visible when you'd expect the primary and secondary be shown side-by-side. The documentation does note the following:
When you build your app’s user interface, the split view controller is typically the root view controller of your app’s window. ... Although it’s possible to install a split view controller as a child in some other container view controllers, doing so is not recommended in most cases.
I have however shipped multiple apps that put a split view controller in a tab bar controller using that classic API (via storyboard and programmatically), and they continue to work as of iOS 15. But it may be wise to move away from this as it's seemingly not an officially supported configuration.
Original answer pre-iOS 14:
You can definitely embed a UISplitViewController inside a UITabBarController. I've done just that for an app I released on the App Store. It has 3 tabs and each one is a split view controller.
Just drag out a tab bar controller into your Storyboard, delete the two controllers it added, then drag out a split view controller. Control drag from the tab bar controller to the split view controller and select the "view controllers" relationship segue.
On Xcode versions less than Xcode 8, you may see black or white bars at the top and bottom of the split view controller in the Interface Builder canvas, but these will not appear when the app is run on a device.
Here is the app running to show the split view embedded inside the tab bar controller on iPhone 6s Plus:
When you put a UISplitViewController inside a UITabBarController and the tab bar is set to be opaque you have an issue where your UISplitViewController content is shifted up the size of the tab bar:
To fix this issue you have to check the Under Opaque Bars checkbox on your UISplitViewController in your storyboard:
And now the UISplitViewController view size is correctly computed:
There is also a problem using this approach in iPhone (>IOS8) where the splitviewcontroller is in collapsed mode. When we push the list view to the details view we cannot hide the tabbarcontroller using the conventional "hidesBottomBarWhenPushed". So I have added the TabBarcontroller as root viewcontroller of a navigationcontroller. Now when I push to details view, I send the message to the root navigation controller and push the view to the details view instance in collapsed mode whereas in regular mode I just push it using showDetailsViewController()
For me, this worked.
XCode 13.2.1
iOS 15.2
splitViewController.extendedLayoutIncludesOpaqueBars = true
Related
I've done a lot of research and read a lot about the use of Tab Bar Controllers with Split View Controllers but cannot seem to find any hint of how to solve my problem...
The following post in the Apple Developer Forum for Cocoa Touch under the heading "Place SplitViewController inside TabBarController" has so far given me the greatest lead.
As of iOS 8, embedding a Split View Controller in a tab bar controller
(or your own container view controller) is supported and expected to
'just work'. Pushing a split view controller onto a navigation stack
remains unsupported.
This appears to go against Apple Documentation including this article titled "Combined View Controller Interfaces" dated November 2014.
You can use the view controllers that the UIKit framework provides by
themselves or in conjunction with other view controllers to create
even more sophisticated interfaces. When combining view controllers,
however, the order of containment is important; only certain
arrangements are valid. The order of containment, from child to
parent, is as follows:
Content view controllers, and container view controllers that have
flexible bounds (such as the page view controller)
Navigation view controller
Tab bar controller
Split view controller
I have a UITabBarController with seven tabs. Of these, five tabs lead to UISplitViewControllers and two tabs lead to UINavigationControllers.
Here is a screenshot of some of the storyboard the shows the tab bar controller leading to three of the five split view controllers...
No problem when I run for target with self.traitCollection.horizontalSizeClass = UIUserInterfaceSizeClassRegular - where the horizontal (width) dimension of the device screen is Regular (not Compact) -> running on an iPad. All seven tabs appear across the bottom tab bar and all view controllers, including split view controllers, work perfectly.
My problem?
Xcode spits an error and freezes app operation when I run for target with self.traitCollection.horizontalSizeClass = UIUserInterfaceSizeClassCompact - where the horizontal (width) dimension of the device screen is Compact -> running on iPhone or iPhone Plus. Same outcome, as expected, for both IB and on iOS device.
Error Message: Split View Controllers cannot be pushed to a Navigation Controller <UIMoreNavigationController: 0x7ffda38b0200>
I know why I've received the error. Where the horizontal size class is "Compact", the seven tabs drops to five on screen, including one (specially prepared by iOS) "More" tab. The remaining three tabs are relegated to the "More" tab that is its own navigation controller and table view controller. My storyboard is attempting to push the split view controller onto this navigation stack.
Any thoughts on a legitimate solution?
I would recommend using a custom tab bar controller with a "More" section that does not push view controllers into a navigation controller. Preferably, one that is a subclass of UITabBarController, so you can use it with Interface Builder.
My approach would be to replace the current "More" table view controller with a view controller of your own that shows a list of overflowing tabs, but does not push their corresponding view controllers into the navigation bar when displayed.
One possible strategy is to become the delegate of the UITabBarController's more navigation controller (found in the tab bar controller's moreNavigationController property). Then use a delegate method, such as navigationController:willShowViewController:animated:, to replace the content of the navigation controller's viewControllers array if the view controller to be displayed is anything but your custom "More" view controller.
I haven't tried this, but it seems like a good place to start and does not require too much knowledge of the tabview controller's internals.
I am new to iOS development and have not tried this programmatically yet. I would prefer to get this working in a storyboard.
I'm following this somewhat outdated tutorial from XCode 4.5 in XCode 6.1 to create a series of views connected by one navigation controller.
http://youtu.be/rgd6mCuzlEc
Once I create the second view controller, I am unable to double click the navigation bar to change the name and I am unable to add a bar button to it.
I have a Segue going from bar button "Item" from view 1 to 2. Notice in the "View Controller Scene" there is no navigation item. If I add any elements to the view controller they fall under "View" and not under "View Controller", unlike view controller 1 where it falls under "one".
Is this a limitation on XCode? Am I using the wrong Segue (Show)? Is there a hidden setting or customization I'm missing?
I actually have this working for 2 view controllers and failing the 3rd in a separate project but I don't know what I did to do that so I'm pretty sure it's possible I just cannot reproduce..
EDIT: Workaround Instead of the new adaptive SHOW segue, use the deprecated PUSH segue, add the bar button items, then change back to the adaptive SHOW segue.
Try adding a Navigation Item to the controller and it should work properly
Let's say I have 4 items in my UITabBar: A B C D.
Via the delegate methods, D pushes a new viewController from the UITabBar's navigationController, (removing the tabBar for that one view).
This works fine, but how can I keep the UITabBar from showing a blank view for D when going back from the new view?
I tried setting the selectedIndex to the previous index on push, but that just hangs the app (seems to work fine for modals, just not when pushing.)
If I interpret this correctly, when you're pushing a view as a result of selecting a tab, you're simultaneously changing the tab that's selected. This is probably trying to then change the view hierarchy that you're currently pushing a view on (that would be the result of selecting the other tab that you're trying to set it to). So it's probably crashing because it's replacing a view that's in the process of being displayed.
Also, from the sound of it you're using a UITabBarController inside of a UINavigationController. Apple says that you're not supposed to place a UITabBarController inside another view controller. The following is taken from Apple's docs on UITabBarController:
When deploying a tab bar interface, you must install this view as the root of your window. Unlike other view controllers, a tab bar interface should never be installed as a child of another view controller.
If this is the case, you should redesign your app so that you're not containing the tab bar controller in any other view controllers, or your app may behave oddly or stop working at some point.
If you wanted to make things behave exactly how you want them to, you could use a UITabBar directly, and implement your own UITabBarDelegate.
I have a UITabBar in my iPad app, and I need to have a Master-Detail concept on one of my UITabBarviews.
I can't use UISplitViewController because Apple says:
The split view controller’s view should always be installed as the root view
of your application window. You should never present a split view inside of
a navigation or tab bar interface.
Is it ok to have two UITableViews on same view as Master-Detail concept on iPad? Is there any other approach for this idea?
I don't know of any limitation about having two table views in a tab. I'd approach it like this:
for that tab, have an overall UIViewController that can run things when the user switches to that tab
it sets up the master detail table views as appropriate.
Interface builder does not allow you to add a UISplitViewController as the root controller of a UINavigationController.
I've also tried programmatically creating the UINavigationController and setting its root view controller to be the UISplitViewController.
The result is an empty window with just the nav bar.
I've also tried a split view controller replacement, MGSplitViewController. It mostly works, except that within the split view controller, the master view is another UINavigationController. Its nav bar shows up too thick. Changing orientation and back clears it up.
I've been trying all sorts of different approaches to having a view that looks like a split view and other views that I switch between. I've tried within a tab view controller, writing my own controller to manage subviews of the window and having the split view as a managed view, and now the navigation controller. All attempts have had some issues. The most consistent issue is regarding the orientation of the view. My app is running in landscape mode and typically the child views think its still portrait.
Any ideas appreciated.
No.
The bottom line: a UISplitViewController must be the root view of an app (or perhaps more specifically, a window). It can not live inside a UINavigationController or anything else.
This is the case with the current SDK, and there's no guarantee that will change in future SDKs.
It seems strange to add a split view to a navigation stack. The master pane of a split view controller is generally a navigation controller, so (without knowing more about your design), I'd probably use that to control your navigation hierarchy.