Triple-column split view controller in portrait mode

Hi,

I have configured my UISplitViewController to show three columns next to each other when the iPad is in landscape mode:

Code Block  
[...]
splitViewController.preferredDisplayMode = .twoBesideSecondary
splitViewController.preferredSplitBehavior = .tile

However, when the iPad is in portrait mode, I'd like the same behavior as the .automatic display mode and split behavior.

Right now, it shows the three columns even in portrait mode, which doesn't look right. Is it a bug or is there some way to set the display mode and split behavior for different iPad orientations?

Thanks.



Accepted Reply

I suggest subclassing UISplitViewController, overriding -viewWillTransitionToSize:withTransitionCoordinator:, and updating preferredDisplayMode and preferredSplitBehavior according to the destination size. I don't recommend looking for interface orientation, because for example, your app may show up on the iPad half and half with another app.

Replies

I suggest subclassing UISplitViewController, overriding -viewWillTransitionToSize:withTransitionCoordinator:, and updating preferredDisplayMode and preferredSplitBehavior according to the destination size. I don't recommend looking for interface orientation, because for example, your app may show up on the iPad half and half with another app.
Thank you.

I've added a function that takes the size as its input to update the split view controller properties, like this:

Code Block swift
    private func updatePreferredBehavior(for size: CGSize) {
        if size.width >= 1194 {
            preferredDisplayMode = .twoBesideSecondary
            preferredSplitBehavior = .tile
        }
        else {
            preferredDisplayMode = .automatic
            preferredSplitBehavior = .automatic
        }
    }


What is the recommended event to call such a method though? Should I call it in viewDidLoad: with the view.bounds.size or is there a better way?

Thanks.
Override the function viewWillTransition(to size:, with coordinator:). Make your changes, then call super.

You may want to use the coordinator's animate method to make your change apply during the animation.
Sorry, I meant what is the recommended way to initially set these properties, as viewWillTransition(to size:, with coordinator:) only gets called when there is a size change, not initially? I'm thinking about setting them initially in viewDidLoad:, but does the size provided by viewWillTransition(to size:, with coordinator:) is always equals to view.bounds.size?

Thanks.
Aha, understood. You don't really know how big the view controller will be until it is asked to lay out for the first time, so viewDidLoad is far too early. Try overriding viewWillLayoutSubviews, and if you haven't already done the setup, do it based on self.view.bounds.size.
I tried with viewWillLayoutSubviews: at first, but it caused a recursive call which lead to EXCBADACCESS, so I fought viewDidLoad: might be more appropriate.

I've added a boolean to check whether the properties have been set in viewWillLayoutSubviews:, and although it's not that elegant, it works as expected.

Thank you very much for your help.
I was having a similar issue, was basically trying to recreate the triple column behavior of the Contacts app on iPad in iOS14. Here's what I found works for me:

I figured out that it's important to set BOTH .preferredDisplayMode and .preferredSplitBehavior when the view transitions. Setting just one or the other will result in lots of weird behavior.

What I figured out works for this case is setting preferredDisplayMode = .twoBesideSecondary and preferredSplitBehavior = .tile when the screen is wide enough to fit all 3 columns, and when it is not, set preferredDisplayMode = .oneBesideSecondary and preferredSplitBehavior = .displace.

I used a function that checks if the view.frame.size.width < 1194 to determine if the screen is full width or not, as using orientation only would cause problems if the app is side by side with another. I'll probably tweak that value over time so don't take it as gospel, but it works for now. I call this function in viewDidLoad for initially setting the behaviors, then again in viewWillTransition(toSize:), using the size property there to update the behaviors.