Swift Source List
Created By: Debasis Das (29-Mar-2015)
In this article we will create a sample application in Swift to display a Source List (view based NSOutlineView) with sample data.
The output of the sample code will have have the following UI
Steps to create a Swift Source List implementation
In this sample app, we will use a hierarchy of information for Department, Accounts in a Department and Employees in an Account.
There can be multiple departments in a company and each department can have multiple accounts, each account can have multiple employees. Each Employee can only belong to one account and one account can only belong to one department
Step 1: Create the required models.
// Model.swift
// SwiftSourceList
// Created by Debasis Das on 3/29/15.
// Copyright (c) 2015 Knowstack. All rights reserved.
import Cocoa
class Department: NSObject {
let name:String
var accounts: [Account] = []
let icon:NSImage?
init (name:String,icon:NSImage?){
self.name = name
self.icon = icon
}
}
class Account: NSObject {
let name:String
var employees: [Employee] = []
let icon:NSImage?
init (name:String,icon:NSImage?){
self.name = name
self.icon = icon
}
}
class Employee: NSObject {
let firstName:String
let lastName:String
let icon:NSImage?
let email:String
init (firstName:String, lastName:String, icon:NSImage?, email:String){
self.firstName = firstName
self.lastName = lastName
self.icon = icon
self.email = email
}
}
// ViewController.swift // SwiftSourceList // Created by Debasis Das on 3/29/15. // Copyright (c) 2015 Knowstack. All rights reserved. import Cocoa class ViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource { @IBOutlet weak var firstNameTextField:NSTextField? @IBOutlet weak var lastNameTextField:NSTextField? @IBOutlet weak var emailIdTextField:NSTextField? @IBOutlet weak var sourceListView:NSOutlineView? var department1 = Department (name:"Department 1",icon:NSImage (named: "Department-50")) var department2 = Department (name:"Department 2",icon:NSImage (named: "Department-50")) override func viewDidLoad() { super.viewDidLoad() let account1 = Account(name:"Account 1",icon:NSImage (named: "account")) let employee10 = Employee (firstName: "Debasis", lastName: "Das", icon: NSImage (named: "employee"), email: "debasis_das@knowstack.com") let employee11 = Employee (firstName: "Mary", lastName: "Jane", icon: NSImage (named: "employee"), email: "maryjane@knowstack.com") account1.employees.append(employee10) account1.employees.append(employee11) let account2 = Account(name:"Account 2",icon:NSImage (named: "account")) let employee20 = Employee (firstName: "John", lastName: "Doe", icon: NSImage (named: "employee"), email: "johndoe@knowstack.com") let employee21 = Employee (firstName: "Jane", lastName: "Doe", icon: NSImage (named: "employee"), email: "janedoe@knowstack.com") account2.employees.append(employee20) account2.employees.append(employee21) department1.accounts.append(account1) department1.accounts.append(account2) self.sourceListView?.expandItem(nil, expandChildren: true) } override var representedObject: AnyObject? { didSet { // Update the view, if already loaded. } } func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject { if let item: AnyObject = item { switch item { case let department as Department: return department.accounts[index] case let account as Account: return account.employees[index] default: return self } } else { switch index { case 0: return department1 default: return department2 } } } func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool { switch item { case let department as Department: return (department.accounts.count > 0) ? true : false case let account as Account: return (account.employees.count > 0) ? true : false default: return false } } func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int { if let item: AnyObject = item { switch item { case let department as Department: return department.accounts.count case let account as Account: return account.employees.count default: return 0 } } else { return 2 //Department1 , Department 2 } } // NSOutlineViewDelegate func outlineView(outlineView: NSOutlineView, viewForTableColumn: NSTableColumn?, item: AnyObject) -> NSView? { switch item { case let department as Department: let view = outlineView.makeViewWithIdentifier("HeaderCell", owner: self) as NSTableCellView if let textField = view.textField { textField.stringValue = department.name } return view case let account as Account: let view = outlineView.makeViewWithIdentifier("DataCell", owner: self) as NSTableCellView if let textField = view.textField { textField.stringValue = account.name } if let image = account.icon { view.imageView!.image = image } return view case let employee as Employee: let view = outlineView.makeViewWithIdentifier("DataCell", owner: self) as NSTableCellView if let textField = view.textField { textField.stringValue = employee.firstName + " " + employee.lastName } if let image = employee.icon { view.imageView!.image = image } return view default: return nil } } func outlineView(outlineView: NSOutlineView, isGroupItem item: AnyObject) -> Bool { switch item { case let department as Department: return true default: return false } } func outlineViewSelectionDidChange(notification: NSNotification){ println(notification) var selectedIndex = notification.object?.selectedRow var object:AnyObject? = notification.object?.itemAtRow(selectedIndex!) println(object) if (object is Employee){ println("selected Object is a Employee " + (object as Employee).firstName); self.firstNameTextField?.stringValue = (object as Employee).firstName self.lastNameTextField?.stringValue = (object as Employee).lastName self.emailIdTextField?.stringValue = (object as Employee).email } else{ println("Do nothing on Department or Account Selection") } } }
Download the Source Code here SwiftSourceList
Great tutorial. Thanks for the help. Any chance you have a tutorial on how to add items to outline view using a button and not code?
In this sample
Depending on whether you want to add an account to a selected department or an employee to a selected account,
In the button click action (For adding a new account to a selected department)
1. Create a new account object
2. Get hold of the department object by outlineViewSelection – index
3. Add the account object created from step 1 to the department.accounts array
4. reload the outline view.
Similarly for adding an employee to an account
1. Create a new employee object
2. get hold of the selected account
3. add the employee object to the account.employees array
4. reload the outline view
The above approach should work
This article explains a little bit, but lacks in two points. This uses a predefined hierarchy (Department->Account->Employee). What if we want all levels to be the same object type? i.e. a Category object could have a subcategory of the same type as the parent, and go deeper (even though it’s probably not a good idea to use outline view for a 10+ level deep hierarchy). Can’t we have a single object type?
Second big problem is, this is rarely real-world compliant. Many programs will actually have to build up the hierarchy at runtime, gathering data from Core Data or iCloud user data, or an online database. It’s really seldom that programmers will actually be able to build the tree at development time.
Thanks David for the feedback. I agree this doestnt mimic a real world problem one to one. True that a real life scenario would not have the tree all layered out perfectly.
This post was for beginners to get started with a Source List in Swift.
I will be working on couple of sample code where the data is loaded at runtime and thus the source list will be created at runtime.
Thanks again for the feedback
Thank you so much, I programatically done NSOutlineView but spending hours debugging what went wrong of my nested object tree.
`isItemExpandable` datasource already implemented true but no avail for expand/collapse. Searched so many pages but none mentioned then landed here and saw this magic line:
“func outlineView(outlineView: NSOutlineView, isGroupItem item: AnyObject) -> Bool {} ”
And boom, nested object expand/collapse as intended!
It’s my bad that didnt spent time to go through all on NSOutlineView docs, but I though the datasource method `isItemExpandable` is the one responsible.
Thanks 🙂