In this post, we will learn how to create a customizable three-column editor layout in SwiftUI for macOS, similar to those found in productivity apps like Xcode, Figma, Keynote, and Sketch. Although SwiftUI’s NavigationSplitView supports a three-column mode, it doesn’t quite fit the needs of a three-column editor style UI. We will explore how to set up the layout using NavigationSplitView and HSplitView to achieve the desired outcome.
A typical three-column layout consists of a Navigator for selecting objects or entities, a Canvas for displaying the current state of a project, and an Inspector for viewing more details and changing specific values of the selected item. Users should be able to collapse and expand each column to manage screen space efficiently.
Let’s set this up with NavigationSplitView
.
|
|
This creates:
Which is great and pretty close to what Keynote looks like:
Pretty close, but not quite there…
NavigationSplitView
is supposed to support a modifier navigationSplitViewColumnWidth(min:ideal:max:)
that can be placed on each of the columns to set the min/idea/max width. However it doesn’t work on the detail column.
|
|
Results in a split view with width greater than 300:
Additionally there is no way to set the detail column to be hidden with a toolbar button.
For instance creating a new boolean @State
variable and setting maxWidth to 0
when false for the inspector results in a missing third panel without relaying out the canvas.
|
|
What Worked
What worked up well enough for me was to use the, sparsely documented, HSplitView
inside of a two column NavigationSplitView
.
|
|
Now the inspector can be open and closed
Open | Closed |
---|---|
There are still some downsides to this approach.
It is impossible to set a maxWidth on the inspector and have the HSplitView respect that width. When dragging the split view divider you will get empty space where the view failed to grow in width. I couldn’t capture a screenshot of this in action because dragging breaks the divider gesture but a screen recording capture shows it. Note the inspector should have not moved with the divider and been fixed at 300 points on the right here.
There is no way to set a minnimumThickness
and canCollapse
properties to enable dragging to the side of the window and have the inspector shrink to minnimum size first then collapse to zero width like with NSSplitViewController
.
p.s. I attempted to reimplement HSPlitView
by adopting NSViewControllerRepresentable
on top of NSSplitViewController
, this works but the challenges bridging from SwiftUI through AppKit/UIKit to another SwiftUI hierarchy and maintaining the Environment/Preferences will be the subject of another blog post.
Sample code for this post can be found here.