UISplitViewController broken when in tab view controllers

I'm trying to rebasline my app to iOS 14. My app's main entry point for the UI is a tab view controller. Three of its four children are then split view controllers. I configured those to 'all visible'. On iPad, this shows both master/detail at all times and on iPhone, you get the collapsed behavior. Just like the Settings app.

Anyhow, I've removed the deprecations and migrated the split view controllers to use the new properties: Style of "Double Column", Display Mode of "One Column Beside", and Behavior of "Tile".

On iPad, the split view controllers will always be collapsed as if they are running on a compact device.

If I make a sample project with the same exact split view controller (along with its configuration as above), but make this SVC the main entry point of the app, all is well on both iPad and iPhone. Collapsed interface only happens on iPhone (compact).

But when I change the sample project to have the SVC now a child of a tab view controller, it breaks (always collapsed on all devices).

Why? Hoping this is a bug and not Apple changing the requirements of what a SVC can be placed inside of. No mention of that in the documentation; just states not to put a SVC inside a navigation controller which I'm not. If I can no longer do this, the entire UI and flow of my app is ruined.

Accepted Reply

Workaround (got help from Apple DTS; they confirmed the bug in iOS 14.x):

First, don't have any subclass of UISplitViewController as the following workaround will fail if having a subclass. Thankfully, I no longer needed to subclass.

In your storyboard, set the split view controller's style to "Unspecified (Discouraged)".

That's it. But read on for workarounds to other issues that I found and needed to work out.

At least in Xcode 12.5, there's a bug when rendering view controllers in storyboards with split view controllers. I have my storyboard set to use "View as: iPhone 11". For split view controllers with the unspecified style, any navigation view controller connected to either the master or detail controller of the split view will not render correctly. Also the connected root view controller will also not render. Instead of seeing your view controller's content, the entire content area is blank, no navigation title is shown either, and you just see the back button.

To work around this issue, I created separate storyboards to hold both the master and detail navigation view controllers and their child controllers. Then, set up storyboard references. Not ideal, but it does make for smaller sized storyboards and the nav controllers and their children now render correctly so you can edit them.

Finally, in my app, I needed to preserve the bit of logic I initially had in my split view controller subclass. Which was to make it its own delegate and return 'true' from splitViewController(_:collapseSecondary:onto:) when dealing with compact size classes.

To deal with this as no subclass works, I created a separate class to represent my delegate:

final class IISplitViewControllerDelegate : NSObject, UISplitViewControllerDelegate {

    func splitViewController(_ aSplitViewController: UISplitViewController, collapseSecondary aSecondaryViewController: UIViewController, onto aPrimaryViewController: UIViewController) -> Bool {
        aSplitViewController.traitCollection.horizontalSizeClass == .compact
    }
}

Then, in the storyboard, I added an object to each split view controller, set it's class to be my delegate, and control-dragged the view controller to that object and set it as the delegate. Now, when these split view controllers are awoken from the nib (storyboard), an instance of my delegate will be made and assigned as the delegate. All works perfectly now.

Replies

Filed bug FB9094956.

Will also file a tech support incident shortly as this is a show-stopper. Cannot find any workarounds.
Add a Comment

Workaround (got help from Apple DTS; they confirmed the bug in iOS 14.x):

First, don't have any subclass of UISplitViewController as the following workaround will fail if having a subclass. Thankfully, I no longer needed to subclass.

In your storyboard, set the split view controller's style to "Unspecified (Discouraged)".

That's it. But read on for workarounds to other issues that I found and needed to work out.

At least in Xcode 12.5, there's a bug when rendering view controllers in storyboards with split view controllers. I have my storyboard set to use "View as: iPhone 11". For split view controllers with the unspecified style, any navigation view controller connected to either the master or detail controller of the split view will not render correctly. Also the connected root view controller will also not render. Instead of seeing your view controller's content, the entire content area is blank, no navigation title is shown either, and you just see the back button.

To work around this issue, I created separate storyboards to hold both the master and detail navigation view controllers and their child controllers. Then, set up storyboard references. Not ideal, but it does make for smaller sized storyboards and the nav controllers and their children now render correctly so you can edit them.

Finally, in my app, I needed to preserve the bit of logic I initially had in my split view controller subclass. Which was to make it its own delegate and return 'true' from splitViewController(_:collapseSecondary:onto:) when dealing with compact size classes.

To deal with this as no subclass works, I created a separate class to represent my delegate:

final class IISplitViewControllerDelegate : NSObject, UISplitViewControllerDelegate {

    func splitViewController(_ aSplitViewController: UISplitViewController, collapseSecondary aSecondaryViewController: UIViewController, onto aPrimaryViewController: UIViewController) -> Bool {
        aSplitViewController.traitCollection.horizontalSizeClass == .compact
    }
}

Then, in the storyboard, I added an object to each split view controller, set it's class to be my delegate, and control-dragged the view controller to that object and set it as the delegate. Now, when these split view controllers are awoken from the nib (storyboard), an instance of my delegate will be made and assigned as the delegate. All works perfectly now.

In case someone still searching for the solution. Place your UISplitViewController in container viewcontroller. Just plain UIViewController but with UISplitViewController view added on it's own view

override func viewDidLoad() {
    super.viewDidLoad()
    
    addChild(splitVC)
    view.addSubview(splitVC.view)
       
    NSLayoutConstraint.activate([
        splitVC.view.topAnchor.constraint(equalTo: view.topAnchor),
        splitVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        splitVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
        splitVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
    ])
    
    splitVC.didMove(toParent: self)
}