macOS NSTableView Tutorial
Table views are one of the most important macOS UI controls. Get up to speed with how to use them with this macOS NSTableView tutorial. By Warren Burton.
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
macOS NSTableView Tutorial
25 mins
- Getting Started
- Creating the Table View
- Anatomy of NSTableView
- Playing With Columns in a Table View
- Changing How Information is Represented
- Assigning Identifiers
- Populating the Table View
- Table View Interaction
- Responding to User Selection
- Responding to Double-Click
- Sorting Data
- Where to Go From Here?
Playing With Columns in a Table View
By default, Interface Builder creates a table view with two columns, but you need three columns to display name, date and size file information.
Go back to Main.storyboard.
Select the table view in the View Controller Scene. Make sure that you select the table view and not the scroll view that contains it.
Open the Attributes Inspector. Change the number of Columns to 3. It’s as simple as that! Your table view now has three columns.
Next, check the Multiple checkbox in the Selection section, because you want to select multiple files at once. Also check Alternating Rows in the Highlight section. When enabled, this tells the table view to use alternating row colors for its background, just like Finder.
Rename the column headers so the text is more descriptive. Select the first column in the View Controller Scene.
Open the Attributes Inspector and change the column Title to Name.
Repeat the operation for the second and third column, changing the Title to Modification Date and Size, respectively.
Note: There is an alternative method for changing the column title. You can double-click directly on the header on the table view to make it editable. Both ways have exactly the same end result, so go with whichever method you prefer.
Note: There is an alternative method for changing the column title. You can double-click directly on the header on the table view to make it editable. Both ways have exactly the same end result, so go with whichever method you prefer.
Last, if you can’t see the Size column yet, select the Modification Date column and resize to 200. It beats fishing around for the resize handle with your mouse. :]
Build and run. Here’s what you should see:
Changing How Information is Represented
In its current state, the table view has three columns, each containing a cell view that shows text in a text field.
But it’s kind of bland, so spice it up by showing the icon of the file next to the file name. Your table will look much cleaner after this little upgrade.
You need to replace the cell view in the first column with a new cell type that contains an image and a text field.
You’re in luck because Interface Builder has this type of cell built in.
Select the Table Cell View in the Name column and delete it.
Open the Object Library and drag and drop an Image & Text Table Cell View into either the first column of the table view or the View Controller Scene tree, just under the Name table view column.
Now you’re whipping things into shape!
Assigning Identifiers
Every cell type needs an assigned identifier. Otherwise, you’ll be unable to create a cell view that corresponds to a specific column when you’re coding.
Select the cell view in the first column, and in the Identity Inspector change the Identifier to NameCellID.
Repeat the process for the cell views in the second and third columns, naming the identifiers DateCellID and SizeCellID respectively.
Populating the Table View
The table view currently knows nothing about the data you need to show or how to display it, but it does need to be looped in! So, you’ll implement these two protocols to provide that information:
-
NSTableViewDataSource
: tells the table view how many rows it needs to represent. -
NSTableViewDelegate
: provides the view cell that will be displayed for a specific row and column.
The visualization process is a collaboration between the table view, delegate and data source:
- The table view calls the data source method
numberOfRows(in:)
that returns the number of rows the table will display. - The table view calls the delegate method
tableView(_:viewFor:row:)
for every row and column. The delegate creates the view for that position, populates it with the appropriate data, and then returns it to the table view.
Both methods must be implemented in order to show your data in the table view.
Open ViewController.swift in the Assistant editor and Control-drag from the table view into the ViewController
class implementation to insert an outlet.
Make sure that the Type is NSTableView and the Connection is Outlet. Name the outlet tableView
.
You can now refer to the table view in code using this outlet.
Switch back to the Standard Editor and open ViewController.swift. Implement the required data source method in the ViewController
by adding this code at the end of the class:
extension ViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return directoryItems?.count ?? 0
}
}
This creates an extension that conforms to the NSTableViewDataSource
protocol and implements the required method numberOfRows(in:)
to return the number files in the directory, which is the size of the directoryItems
array.
Now you need to implement the delegate. Add the following extension at the end of ViewController.swift:
extension ViewController: NSTableViewDelegate {
fileprivate enum CellIdentifiers {
static let NameCell = "NameCellID"
static let DateCell = "DateCellID"
static let SizeCell = "SizeCellID"
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var image: NSImage?
var text: String = ""
var cellIdentifier: String = ""
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .long
// 1
guard let item = directoryItems?[row] else {
return nil
}
// 2
if tableColumn == tableView.tableColumns[0] {
image = item.icon
text = item.name
cellIdentifier = CellIdentifiers.NameCell
} else if tableColumn == tableView.tableColumns[1] {
text = dateFormatter.string(from: item.date)
cellIdentifier = CellIdentifiers.DateCell
} else if tableColumn == tableView.tableColumns[2] {
text = item.isFolder ? "--" : sizeFormatter.string(fromByteCount: item.size)
cellIdentifier = CellIdentifiers.SizeCell
}
// 3
if let cell = tableView.make(withIdentifier: cellIdentifier, owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
cell.imageView?.image = image ?? nil
return cell
}
return nil
}
}
This code declares an extension that conforms to the NSTableViewDelegate
protocol and implements the method tableView(_:viewFor:row)
. It’s then called by the table view for every row and column to get the appropriate cell.
There’s a lot going on the method, so here’s a step-by-step breakdown:
- If there is no data to display, it returns no cells.
- Based on the column where the cell will display (Name, Date or Size), it sets the cell identifier, text and image.
- It gets a cell view by calling
make(withIdentifier:owner:)
. This method creates or reuses a cell with that identifier. Then it fills it with the information provided in the previous step and returns it.
Next up, add this code inside viewDidLoad()
:
tableView.delegate = self
tableView.dataSource = self
Here you tell the table view that its data source and delegate will be the view controller.
The last step is to tell the table view to refresh the data when a new directory is selected.
First, add this method to the ViewController
implementation:
func reloadFileList() {
directoryItems = directory?.contentsOrderedBy(sortOrder, ascending: sortAscending)
tableView.reloadData()
}
This helper method refreshes the file list.
First, it calls the directory
method contentsOrderedBy(_:ascending)
and returns a sorted array with the directory files. Then it calls the table view method reloadData()
to tell it to refresh.
Note that you only need to call this method when a new directory is selected.
Go to the representedObject
observer didSet
, and replace this line of code:
print("Represented object: \(url)")
With this:
directory = Directory(folderURL: url)
reloadFileList()
You’ve just created an instance of Directory
pointing to the folder URL, and it calls the reloadFileList()
method to refresh the table view data.
Build and run.
Open a folder using the menu File > Open… or the Command+O keyboard shortcut and watch the magic happen! Now the table is full of contents from the folder you just selected. Resize the columns to see all the information about each file or folder.
Nice job!