Getting Started with Cocoa Bindings for Populating an NSTableView
A gentle introduction to NSTableView.
Letâs say you have some data:
let menu = [Food(id: 1, name: "Banana Split", price: 12), Food(id: 2, name: "Fried Banana", price: 7)]
If you want to learn to build a simple native Mac app with Storyboards in Xcode that displays the data with an NSTableView
, this post is for you.
Letâs begin!
How?
Here Iâll assume no knowledge of Xcode or macOS development, because this is the post I wish I had when I started my first Cocoa project.
If you havenât already, make a new project like so: File > New > Project⊠> macOS > Cocoa App.
The Data Model
We need a way to represent food items that can be key-value compliant for Cocoa, which here means that our data needs to be accessible from the Objective-C runtime. Make a file in the root of the project called Food.swift
with the following contents:
class Food: NSObject {
@objc dynamic var id: Int
@objc dynamic var name: String
@objc dynamic var price: Float
init(id: Int, name: String, price: Float) {
self.id = id
self.name = name
self.price = price
}
}
Take note of the @objc dynamic
annotations and the fact that Food
inherits from NSObject
. Other than that, itâs just a class with a constructor.
File Creation Instructions
Do File > New > File⊠(or â-N) and select âSwift Fileâ when prompted. Click âNextâ and give the new file a name, while making sure its destination is the root of the project (next to the AppDelegate.swift
file).
Hello, Cocoa!
Make another file called MainViewController.swift
that contains the following boilerplate:
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// TODO: populate the table view
}
}
We need to make a menuItems
array to bind to the table view, so add the following declaration below the opening class
bracket:
@objc dynamic var menuItems: [Food] = []
The Layout
Open âMain.storyboardâ via the Project Navigator panel.
Show the object library with â-Shift-L and search for âTable Viewâ in the window that pops up. Drag the entry of the same name onto the NSView in Interface Builder, which is below the representation of the Window Controller.
Autoresizing!
While youâre at it, make sure the right-most panel is shown by making sure the image button with the tooltip âHide or show the inspectorsâ is outlined with blue. Iâll call this the âinspector panelâ from now on. Hereâs a cheat sheet I threw together:
Now you can click on the Size Inspector, which looks like a ruler and is the fourth icon from the right on the inspector panel, scroll down, and in the âAutoresizingâ section, click all the light red arrows to make the table view expand along with the window.
Your window should look something like this:
Columns
To display the name and price of each food item, we will need two table view columns. Expand the âTable Viewâ in the Document Outline, and if there arenât already two âTable Columnâ objects in the view, duplicate the current one with â-D.
Go ahead and double-click on each column header and type the names âNameâ and âPriceâ, respectively.
Outlets and Bindings
Now we need to do the following:
- Add an Array Controller.
- Bind the Array Controller to the menu list.
- Add an
IBOutlet
so the table view can be referenced fromViewController
. - Bind the table cell views to the
Food
attributes that correspond to each column. - Bind the table view to the array controller.
Add an Array Controller
Click on the âView Controllerâ group above the layout panel to reveal a new list of icons, and drop âNSArrayControllerâ from the Object Library next to the other top-level objects:
Bind the Array Controller to the data array defined in MainViewController.swift
Make sure the new Array Controller is selected in the top-level object group and open up the Bindings inspector pane. Under Controller Content > Controller Array, make sure the âBind Toâ checkbox is checked, change the corresponding controller in the dropdown to âView Controllerâ, and write menuItems
for the âmodel key pathâ. This will be the name of the list our UI auto-updates from.
Add an IBOutlet
so the table view can be referenced from ViewController
Make sure the âTable Viewâ item is selected in the Document Outline, right click, and drag the âNew Referencing Outletâ circle to create a line segment that ends at the top of the class definition:
Now make a name for the outlet, like tableView
, change âWeakâ to âStrongâ (because the table view and view controller must both exist at the same time), and click âConnectâ.
The ViewController
class should now look like this:
class ViewController: NSViewController {
@objc dynamic var menuItems: [Food] = []
@IBOutlet var tableView: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
// TODO: populate the table view
}
}
Bind the table cell views to the instance variable names that correspond to each column
Now letâs make the app actually display what it is supposed to. For both columns, select the NSTextFieldCell
âTable View Cellâ (which is below the NSTextField
âTable View Cellâ, which is below the NSTableCellView
âTable Cell Viewâ, which is below each NSTableColumn
; thatâs a mouthful!), and in the Bindings inspector pane (the second icon from the right in the inspector panel), check âBind Toâ, make sure the target is âTable Cell Viewâ, and set both âModel Key Pathâs to âobjectValue.nameâ and âobjectValue.priceâ, respectively. See the following annotated screenshot for a recap of what you should do before moving on to the next section:
Bind the table view to the array controller
Before we can take advantage of the connection between the array controller and the code, we need to connect the table view to the array controller. Open up the Bindings pane for the table view and under âTable Contentâ expand the âContentâ group. Check âBind Toâ and make sure the corresponding dropdownâs value is âArray Controllerâ.
Remember, we can now refer to our NSTableView
as tableView
from the code-behind (this will help when you want to implement actions on the table view and do things in response to changes of its state) and load Food
to menuItems
, which will automatically trigger an update of the contents of the table view, thanks to all the binding work you just did (you did follow along in Xcode, right?)!
Data Loading!
Now that weâve come this far we can pretend like weâre loading from a persistent data store:
class ViewController: NSViewController {
@objc dynamic var menuItems: [Food] = []
@IBOutlet var tableView: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
var menu: [Food] = []
// TODO: read this data from a flat file or database
menu = [Food(id: 1, name: "Banana Split", price: 12), Food(id: 2, name: "Fried Banana", price: 7)]
// populate the table view
menuItems = menu
}
}
Thatâs it!
Compatibility
This guide was written for Xcode Version 11.0 beta (11M336w) because I like shiny new tools, but it should work with Xcode 10 or whatever the stable version is when youâre reading this. I guess thatâs one advantage you get when you stick with old and boring things like Cocoa.
đ«
Cocoa on!
Solomon