Populating an NSOutlineView with data in a Cocoa Mac OS X application using Swift

When using an NSOutlineView you can set the datasource and delegate outlets to have an NSViewController populate the NSOutlineView. In this post, we will implement a simple three-column Outline View that displays a two level data structure of trees and flowers.

Step 1: Create the project

In XCode, create a new Cocoa Application using Swift and Storyboards. Name the project MyOutlineView.

In the View Controller Scene, drag an Outline View onto the View. Resize the Outline View so that it covers the entire view.

Select the Outline View (remember it is nested inside a Bordered Scroll View and Clip View, so click on the Outline View three times and make sure that the Table View inspector is selected.)

Control-drag from the Outline View to the First Responder in the View Controller and connect the delegate and datasource outlets. This tells the Outline View that the View Controller is responsible for providing its data.

Select the Attributes inspector for the Outline View and change the number of Columns to 3.

Select the first column and change the following property:

  • Identity Inspector: Identifier = name

Select the second column and change the following properties:

  • Identity Inspector: Identifier = type
  • Attributes Inspector: Title = Type

Select the third column and change the following properties:

  • Identity Inspector: Identifier = species
  • Attributes Inspector: Title = Species

Add the NSOutlineViewDataSource and NSOutlineViewDelegate protocols to ViewController.swift. ViewController.swift should look like this:

//
//  ViewController.swift
//  MyOutlineView
//
//

import Cocoa

class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    override var representedObject: AnyObject? {
        didSet {
        // Update the view, if already loaded.
        }
    }
}

Step 2: Set up the data to display in the Outline View

Create a variable in ViewController.swift. This data will be used to populate the Outline View. In this example, we have used a data structure that lists trees and flowers by type and species.

var data : [AnyObject] =
[
    ["name" : "tree",
     "types" :
        [["type" : "oak", "species" : "Quercus alba"],
            ["type": "elm", "species" : "Ulmus glabra"]]
    ],
    ["name" : "flower",
     "types" :
        [["type" :"rose", "species" : "Rosa rugosa"],
            ["type": "tulip", "species" : "Tulipa albanica"],
            ["type": "primula", "species" : "Primula hortensis"]]
    ]
]

Step 3: Implement the NSOutlineView delegate and datasource methods.

As the Outline View processes the data it asks each item of data four questions:

  • Does the item have a child?
  • Is the item expandable?
  • How many children does the item have?
  • How should the item be displayed?

The first question is answered by the function outlineView:child:ofItem.

 func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {        
    var result : AnyObject = ""

    if (item == nil) {
        result =  data[index]
    } else {
        if let _ = item as? [String : AnyObject] {
            result = item!["types"]!![index]
        }
    }
    return result
}

When the Outline View starts processing the data, the item will be nil so the function starts with the top level items ("name" : "tree" and "name" : "flower").

The second question is answered by the function outlineView:isItemExpandable.

func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
    if let _ = item["name"] as? String {
        return true
    } else {
        return false
    }
}

In the example data, if the item has an attribute called name, then it is expandable.

The third question is answered by the function outlineView:numberOfChildrenOfItem

func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {        
    if (item == nil) {
        return data.count
    }

    if let _ = item!["name"] as? String {
        return (item!["types"]!!.count)!
    }                
    return 0
}

Again, when the Outline View starts processing the data, the item will be nil so we return the count of the top level items in our data. If the item has a name attribute, then we return the count of the types collection. If neither tests match the item, then we tell the Outline View that the item has no children by returning 0.

The fourth question is answered by the function outlineView:viewForTableColumn:item.

func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {

    let result = outlineView.makeViewWithIdentifier((tableColumn?.identifier)!, owner: self) as! NSTableCellView
    switch ((tableColumn?.identifier)! as String) {
        case "name":
            if let n = item["name"] as? String {
                result.textField?.stringValue = n
            } else {
                result.textField?.stringValue = ""
            }
        case "type":
            if let t = item["type"] as? String {
                result.textField?.stringValue = t
            } else {
                result.textField?.stringValue = ""
            }
        case "species":
            if let t = item["species"] as? String {
                result.textField?.stringValue = t
            } else {
                result.textField?.stringValue = ""
            }
    default: break       
    }
    return result
}

For each item, we populate the three columns of the Outline View so that:

  • Column one only displays the name attribute of a top level item (tree or flower)
  • Column two only displays the type attriubte of a child item (oak, elm, rose, tulip or primula)
  • Column three only displays the species of a child item (Quercus alba, Ulmus glabra, Rosa rugosa, Tulipa albanica or Primula hortensis).

Conclusion

The NSOutlineView is excellent for displaying hierarchical data in a compact way using a very familiar interface. The four datasource/delegate functions for the OutlineView require knowledge of structure of the data being processed. In the example above we used the following tests:

  • If the item is nil, we are at the root of the data structure.
  • If the item has a name, we are at the top level of the data structure
  • If the item has a type, we are at the child level of the data structure

In running these tests, checking for optional values using if let ... comes in very handy for keeping the code simple and concise.