NSOutlineView on macOS Tutorial
Discover how to display and interact with hierarchical data on macOS with this NSOutlineView on macOS tutorial. By Jean-Pierre Distler.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
NSOutlineView on macOS Tutorial
25 mins
Update 9/30/16: This tutorial has been updated for Xcode 8 and Swift 3.
Update 9/30/16: This tutorial has been updated for Xcode 8 and Swift 3.
When writing applications, you often want to show data that has a list-like structure. For example, you might want to display a list of recipes. This can be easily done with a NSTableView
. But what if you want to group the recipes by appetizer or main course? Now you have a problem, because table views have no sections. Your five-layer-dip recipe is right next to your linguine del mare recipe, which just won’t do!
Thankfully, NSOutlineView
provides a lot more functionality. NSOutlineView
is a common component in macOS applications, and is a subclass of NSTableView
. Like a table view, it uses rows and columns to display its content; unlike a table view, it uses a hierarchical data structure.
To see an outline view in action, open Xcode with an existing project and have a look at the project navigator. Click on the triangle next to the project name to expand it. You’ll see a structure like the image below: beneath the project are groups, and inside the groups are Swift or Objective-C files.
In this NSOutlineView on macOS tutorial, you will learn how to use an outline view to show your own hierarchical data. To do this, you’ll write a RSS Reader-like application that loads RSS Feeds from a file and shows them in an outline view.
Getting Started
The starter project can be downloaded here. Open the project and take a peek. Besides the files created by the template, there is a Feeds.plist, which is the file you’ll load the feeds from. You’ll take a closer look at it later, when creating the model classes.
Open Main.storyboard to see the prepared UI. On the left is a plain outline view, and beside it is a white area, which is the web view. Those are grouped using a horizontal stack view, which is pinned to the window edges. Stack views are the latest and greatest way to deal with Auto Layout, if you haven’t yet given them a try. You can learn all about them in Marin’s great tutorial about NSStackViews.
Your first task: complete the UI. To do this, double-click in the header to change the title. For the first column, change it to Feed; change the second to Date.
That was easy! Now select the outline view, in the document outline — you’ll find it under Bordered Scroll View – Outline View \ Clip View \ Outline View. In the Attributes Inspector, change Indentation to 5, enable Floats Group Rows and disable Reordering.
Inside the document outline on the left, click on the triangle beside Outline View to expand it. Do the same for Feed and Date. Select the Table View Cell below Date.
Change the Identifier to DateCell in Identity Inspector.
Now show the Size Inspector and change Width to 102. Repeat this step for the cell below Feed, changing the Identifier to FeedCell and Width to 320.
Expand the cell below feed and select the text field named Table View Cell.
Use the Pin and Align menus on the Auto Layout toolbar to add an Auto Layout constraint of 2 points leading, plus another constraint to center the text field vertically. You will see the constraints in Size Inspector:
Now select the table cell again (above the text field in the layout hierarchy). Duplicate it by pressing Cmd + C and Cmd + V, then change the Identifier of the duplicate to FeedItemCell. Now you have 3 different cells, one for each type of entry that will be shown in the outline view.
Select Date, and in the Identity Inspector change the Identifier to DateColumn; do the same for Feed and change it to TitleColumn:
The final step is to give the outline view a delegate and a data source. Select the outline view and right- or control-click on it. Drag a line from dataSource to the blue circle that represents your view controller; repeat this to set the delegate.
Run the project and you’ll see …
There’s an empty outline view and an error message in your console, saying you have an illegal data source. What’s wrong?
Before you can fill the outline view and get rid of the error message, you need a data model.
Data Model
The data model for an outline view is a bit different than the one for a table view. Like mentioned in the introduction, an outline view shows a hierarchical data model, and your model classes have to represent this hierarchy. Every hierarchy has a top level or root object. Here this will be a RSS Feed; the name of the feed is the root.
Press Cmd + N to create a new class. Inside the macOS section select Cocoa Class and click Next.
Name the class Feed
and make it a subclass of NSObject
. Then click Next and Create on the next screen.
Replace the automatically generated code with:
import Cocoa
class Feed: NSObject {
let name: String
init(name: String) {
self.name = name
}
}
This adds a name
property to your class and provides an init method that sets the property to a provided value. Your class will store its children in an array, but before you can do this, you need to create a class for those children. Using the same procedure as before, add a new file for the FeedItem
class. Open the newly created FeedItem.swift and replace the content with the following:
import Cocoa
class FeedItem: NSObject {
let url: String
let title: String
let publishingDate: Date
init(dictionary: NSDictionary) {
self.url = dictionary.object(forKey: "url") as! String
self.title = dictionary.object(forKey: "title") as! String
self.publishingDate = dictionary.object(forKey: "date") as! Date
}
}
This is another simple model class: FeedItem
has a url
that you will use to load the corresponding article into the web view; a title
; and a publishingDate
. The initializer takes a dictionary as its parameter. This could be received from a web service or, in this case, from a plist file.
Head back to Feed.swift and add the following property to Feed
:
var children = [FeedItem]()
This creates an empty array to store FeedItem objects.
Now add the following class method to Feed
to load the plist:
class func feedList(_ fileName: String) -> [Feed] {
//1
var feeds = [Feed]()
//2
if let feedList = NSArray(contentsOfFile: fileName) as? [NSDictionary] {
//3
for feedItems in feedList {
//4
let feed = Feed(name: feedItems.object(forKey: "name") as! String)
//5
let items = feedItems.object(forKey: "items") as! [NSDictionary]
//6
for dict in items {
//7
let item = FeedItem(dictionary: dict)
feed.children.append(item)
}
//8
feeds.append(feed)
}
}
//9
return feeds
}
The method gets a file name as its argument and returns an array of Feed
objects. This code:
- Creates an empty
Feed
array. - Tries to load an array of dictionaries from the file.
- If this worked, loops through the entries.
- The dictionary contains a key name that is used to inititalize
Feed
. - The key items contains another array of dictionaries.
- Loops through the dictionaries.
- Initializes a
FeedItem
. This item is appended to thechildren
array of the parentFeed
. - After the loop, every child for the
Feed
is added to thefeeds
array before the nextFeed
starts loading. - Returns the
feeds
. If everything worked as expected, this array will contain 2Feed
objects.
Open ViewController.swift, and below the IBOutlet section add a property to store feeds:
var feeds = [Feed]()
Find viewDidLoad()
and add the following:
if let filePath = Bundle.main.path(forResource: "Feeds", ofType: "plist") {
feeds = Feed.feedList(filePath)
print(feeds)
}
Run the project; you should see something like this in your console:
[<Reader.Feed: 0x600000045010>, <Reader.Feed: 0x6000000450d0>]
You can see that you’ve successfully loaded two Feed
objects into the feeds
property — yay!