How to Use NSSplitView in a macOS App

Splitting your views for more functionality

David Piper
Feb 16 · 7 min read
Image for post
Image for post
Photo by Pietro De Grandi on Unsplash

Many macOS apps — like Safari, Finder, Mail, and Xcode — use split views to divide their content into separate areas. This allows a user to decide how to size different parts of the app themselves.

Coming from an iOS background, I was surprised how many fewer references, articles, and documentation exist for macOS development; thus, I decided to write down my experiments on how to use an NSSplitView.

In this tutorial, we’ll first add a split view via a storyboard and see how to add more panels. Next, we’ll explore how to recreate Xcode’s segmented control to show and hide these areas programmatically. To improve this transition between collapsed and expanded panels, we’ll add an animation. Finally, we’ll see how to detect when a user manually resizes a panel and how to reflect this update in the segmented control.

Adding an NSSplitView

Let’s start by adding an NSSplitView to our storyboard. As you can see in the left image below, when starting a new project, you’ll see a window controller and a view controller in your storyboard. To add the split view, simply search for it in the library (accessible via the + button in the upper right corner of Xcode or by pressing Shift+Cmd+L) and drag it onto the existing view controller.

After adding some constraints to position it inside the previous view controller, you could start creating each panel of the split view by adding subviews directly to it, but it’s a better approach to add container views for each panel.

This way each part of the split view has its own separate view controller, which will reduce the responsibilities and the amount of code your initial view controller has.

Again search the library for container views and add them as subviews to the split view. You can see the final setup on the right side of the image below. I also added labels with the texts “Left Panel” and “Right Panel” to the panels, so they have some content to show.

Image for post
Image for post
Image for post
Image for post
Left: Initial setup of the Main.storyboard file. Right: Layout with split view and container views.

This is what the app looks like right now:

Image for post
Image for post
Current UI of the app

Adding More Panels

By default, a NSSplitView will have two panels. If you look closely through the configuration options in the inspector, you won’t find an option to add another one like you would for an NSSegmentControl, for example. So what do you do if you want to have more than two panels?

Looking at the view hierarchy, we can see our split view holds two custom views. We can simply get another one from the library (+ button or Shift+Cmd+L) and drag it inside of our split view. This will add a new area, which we can set up just like the two previous ones above.

Image for post
Image for post
The view hierarchy of a split view with two panels. To add another one, you need to add a new custom view.

Programmatically Show or Hide Panels

Now that we have three panels to hold the content of our app, let’s look at how to programmatically show or hide these areas.

Of cause, a user can drag the dividers to resize them, but many apps have dedicated buttons to do so as well. Safari, for example, has a button in the upper-left corner to show or hide the sidebar, and Xcode offers a segmented control with three segments to change the visibility of the navigator, debug area, and inspector. Additionally, each segment gets updated if the user closes or opens a panel by dragging the divider.

Image for post
Image for post
Image for post
Image for post
Many apps use buttons to show or hide panels of split views. Left: Safari, Right: Xcode.

Let’s try to recreate Xcode’s panel control segment. To do so, we need to add a segmented control to the view controller. I also recreated the icons Xcode is using to make it look nicer. We want the first segment to control the left panel and the right segment to control the right one so the middle part is always visible. To allow multi selection on the segment, we need to set its mode to Select Any, and since on start all panels should be visible, we also need to set both segments to be selected. Both settings can be done by selecting segmented control and opening its attribute inspector.

Here’s how the storyboard and the app look like now:

Image for post
Image for post
Image for post
Image for post

Let’s look at how to handle events on the segmented control:

Using a segmented control to collapse or expand the split view’s panels

// 1 — First, we need to add an IBAction to handle when a segment is clicked by the user. This action provides us the sender as an argument.

// 2 — We can switch over the segment’s selectedSegment property to know which segment was clicked. Depending on this information, we’ll change the visibility of the left or the right panel. We also need to know whether the segment was selected or unselected. In the first case, we want to show the corresponding panel — otherwise, we want to hide it.

// 3 — If the first segment was clicked, we want to change the visibility of the left panel. If it should be shown, we set 100 points as the new width and call the split view’s method setPosition(_:ofDividerAt:). This will move the divider at the index 0 to a distance of 100 points from the view’s left border. Otherwise, if the left panel should be collapsed, we’ll set the new position to 0 and thus hide the panel. Finally, we need to call splitView.layoutSubtreeIfNeeded() to update the layout of the split view.

// 4 — Just like we did with the left panel, we need to set a new position for the divider at index 1. But since it’s the right panel on the right side of the view, this time we’ll set the new position to view.frame.width — 100 if the panel should be expanded and to view.frame.width if it should be collapsed.

Now you can run the app, click on the segmented control, and you should be able to see the panels collapse and expand — cool!

Animating the Expanding and Collapsing

We can use an animation to make the transition smoother:

Adding an animation to the split view

// 1 — To do so, we need to modify the body of changeLeftPanelVisibility(visible:) and changeRightPanelVisibility(visible:). Instead of updating the dividers position, we call a small helper method which does the same inside an animation.

// 2 — This new method will get the new position and the index of the divider to update.

// 3 — We can use animations by calling runAnimationGroup and use the given animation context to configure the animation — in this example, the animation will take 0.75 seconds. Finally, inside the closure, we also need to update the split view.

This GIF shows how it should look like in action:

Image for post
Image for post
The animation in action

Detecting a Manual Change of the Panel Size

When using Xcode, we can see that the segments are updated when a user drags a divider and closes the navigator, the inspector, or the debug area. Next, we’ll see how we can recreate this behavior.

// 1 — The view controller holding the split view needs to implement the protocol NSSplitViewDeletate to get notified whenever the position of a divider is updated.

// 2 — We also need outlets to the segmented control and the split view.

// 3 — In viewDidLoad we can tell the split view this view controller will be its delegate.

// 4 — One of the methods in NSSplitViewDelegate is splitViewDidResizeSubviews(_:). It gets called whenever a divider is moved.

// 5 — This method receives a Notification as its parameter. This notification has a property, userInfo, which is a dictionary containing the index of the moved divider for the key NSSplitViewDividerIndex.

// 6 — We need two bits of information to update the segmented control: the index of the segment to update and whether it should be selected or unselected. We can get the first piece of information by checking the index of the dragged divider. To get the selection state, we need to check if the panel now has a width of 0 points. If this is the case, the corresponding segment should be unselected — otherwise, it needs to be selected.

// 7 — Lastly, we can set the correct state for the segmented control.

Image for post
Image for post
Updating the segmented control depending on the visibility of the split view`s panels.

Conclusion

NSSplitView is a powerful view to use in you app. In this tutorial, you saw how to use it in your app to show multiple areas next to each other and how to programmatically expand or collapse the panels just like Xcode does.

If you want more information about view controllers in macOS, you can read this article. It’s not specifically about NSSplitView, but it explains how to use container views, what the lifecycle of an NSViewController looks like, and how to use NSStoryboardSegue.

Thanks for reading!

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store