NSTreeController + NSOutlineView: A powerful combination

With all of the recent talk about Catalyst, it might be tempting to dismiss Mac OS AppKit development as a thing of the past. In reality, AppKit provides the Swift programmer with some excellent components for putting together a desktop application.

Alexander Murphy
Building Ibotta
4 min readJan 20, 2020

--

Recently, myself and some other members of the iOS team decided to put together a development console for our mobile application. We decided to use AppKit because we wanted to leverage some of the components found within Xcode and other native mac os applications.

Coming from the iOS development stack, we were all quite familiar with classes like UITableView and UICollectionView as well as their associated use cases. AppKit provides similar components for developing user interfaces, and adds a few more which aren’t available within UIKIt.

Xcode debugging tools (when functional 😅) provide the developer with great interfaces to get to the bottom of a problem in our software. One such interface is the view debugger, pictured below. This view allows us to inspect the application’s view hierarchy, and discover any layout issues.

TLDR;

Just give me the code!

The view debugger within Xcode.

If you look close enough, you will find NSOutlineView deployed everywhere! Here is another example, this time within the property list editor:

The .plist editor within Xcode.

This is a tool we all use fairly often, but what drives this interface under the hood? In AppKit, this component is known as NSOutlineView. In this tutorial, we’re going to explore a basic example of NSOutlineView, as well as binding it to a tree data structure using NSTreeController.

Our Basic Use Case

What we’re going to build!

Our example Cocoa application will display the contents of a tree containing offers, their corresponding rewards, and a list of retailers. Additionally, we will display each node’s child node count in a second column.

🌲 A Simple Tree

As you can see in the screenshot above, our tree explorer will display a simple list of offers, along with their amounts. The basic structure is described by the complex infographic below.

A tree of offers and their corresponding cash rewards.

Let’s begin with a Node data structure to describe our tree. Due to NSTreeController’s requirement that this class is KVO compliant, we will need to add some additional modifiers.

As you can see, this class object is quite similar to most tree representations you have probably seen. We have our value, our array for storing child nodes, and some computed properties to assist our data display and NSTreeController.

🏭 A Small Factory

Next, we add a small factory class to generate our tree. Right now, we have a static tree so it’s quite simple:

Configuring Our Storyboard

This Cocoa application uses a single view controller, to which we add an NSOutlineView and constrain it to the edges of our view controller. From there, we add identifiers to each NSTableCellView using the inspector. As you can see next to the pink arrow in the screenshot below, we have added an identifier for this cell. We’re going to need this to be set when we start making cell views later on.

Within our Main.storyboard file, we add a NSOutlineView and configure column identifiers.

The Main View Controller Implementation

In our View Controller’s viewDidLoad method, we’re configuring a few separate things. First, we’re setting the delegate property of our NSOutlineView to this class, which we will explore the implementation for in a bit.

Next, we bind the tree controller’s configuration keypaths to our Node implementation’s properties. This allows us to populate our NSTreeController implementation with a tree of Node objects.

After some NSOutlineView styling, we bind our tree controller to our @objc dynamic var content = [Node]() instance property. This allows us to mutate our tree and have the change reflected in the tree controller.

Finally, we bind the outline view’s content to our tree controller’s arrangedObjects keypath. After populating our nodes array, the changes will be visible in our tree viewer.

Delegation For Row Configuration

Now that we have configured and hooked up our NSTreeController based data source implementation, let’s examine our NSOutlineViewDelegate implementation which configures our view layer.

In the above code example, we’re simply switching on the column identifier (outlined above in the storyboard screenshot) and binding the row’s textField contents to our node’s value property and childrenCount property, respectively. With this simple syntax, we can bind our NSOutlineView‘s row contents directly to our NSTreeController‘s underlying data.

Conclusion

Cocoa applications, and AppKit specifically, provide the Swift developer with powerful components to build complex visual data representations. Many of these components are visible directly within Xcode, and you may have been using them without even realizing it!

If you would like to review the code demonstrated above in a fully functioning Xcode project, be sure to checkout the example repository.

If you have any questions, feel free to use the comment thread below! We’re happy to share our experience with you.

We’re Hiring!

If these kinds of projects and challenges sound interesting to you, Ibotta is hiring! Check out our jobs page for more information.

--

--