UISplitViewController - replace back-chevron icon with a sidebar toggle icon in portrait

Viewed 316

I'm working on an iPadOS app in which I use UISplitViewController. So far I have been using old APIs and handling everything (including the displayModeButtonItem) manually. Now I wanted to migrate to the newer "column style" APIs (super.init(style: .doubleColumn). I had a few UI issues, but I managed to fix them with a few workarounds, but now I'm blocked on an issue for which I can't find any solution:

I'd like to make use of all built-in mechanisms of the iOS14's UISplitViewController, so I set presentsWithGesture property to true. Because of this I get sidebar toggle icon in landscape orientation (which works exactly how I want it), but in portrait I get a "back-chevron icon" with "back" title. Is there a way to force sidebar toggle icon to be displayed for both orientations?

Portrait orientation - I'd like to have sidebar toggle icon here

Landscape orientation - this is how I'd like the button to look like in both orientations

1 Answers

I set up a sample project and I'm not getting the behavior you are. I'm thinking it's because we do things differently in setting up the UISplitViewController.

First, a major thanks to Matt Neuburg (@matt here on SO) for his two part article. (part one here) This provided me with some big changes to my code.

Basically, I no longer set my UISplitViewController to be the root view in my scenes. Instead, I do not touch SceneDelegate at all, but use the default ViewController:

let primaryVC = PrimaryVC()
let secondaryVC = SecondaryVC()
let compactVC = CompactVC()

override func viewDidLoad() {
    super.viewDidLoad()
    let split = UISplitViewController(style: .doubleColumn)
    self.addChild(split)
    self.view.addSubview(split.view)
    split.view.frame = self.view.bounds
    split.presentsWithGesture = true
    split.preferredSplitBehavior = .tile
    split.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
    split.didMove(toParent: self)
    split.setViewController(primaryVC, for: .primary)
    split.setViewController(secondaryVC, for: .secondary)
    split.setViewController(compactVC, for: .compact)
}

When I do this, I get a "sidebar.left" button and no "Back" button with Chevron. I'm using Xcode 13.1 and tried this with targets of iPadOS 14.0 and 15.1.

If you need to add/replace a bar button, what I do is:

var barBtnSidebarLeft = UIBarButtonItem(image: UIImage(systemName:"sidebar.left"), style: .plain, target: self, action: #selector(leftSidebarTapped)) navigationItem.leftBarButtonItem = barBtnSidebarLeft

But beware that if you do this, you will get two sidebar buttons! With regards to bar buttons, one more note - while the UISplitViewController provides a navigation bar by default for Primary and Secondary, it does not for Compact. (Frankly, I don't think it's good practice to have one when in compact size.)

Finally, read through the link I provided. It really gets into using UISplitViewControllerDelegate to help you pass along your scene state values properly. (I prefer calling it scene state instead of app state because iPads can have multiple app windows.)

EDIT

One last thought. IIRC you can do this:

  • set presentsWithGesture to `false
  • place your own bar buttons as pleased in both view controllers
  • manually (through the delegate functions?) open and close the Primary view controller
Related