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.
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!
If you look close enough, you will find NSOutlineView
deployed everywhere! Here is another example, this time within the property list editor:
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
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.
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.
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.