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.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Table View Interaction

In this section, you’ll work with some interactions to improve the UI.

Responding to User Selection

When the user selects one or more files, the application should update the information in the bottom bar to show the total number of files in the folder and how many are selected.

In order to be notified when the selection changes in the table view, you need to implement tableViewSelectionDidChange(_:) in the delegate. This method will be called by the table view when it detects a change in the selection.

Add this code to the ViewController implementation:

func updateStatus() {
  
  let text: String
  
  // 1
  let itemsSelected = tableView.selectedRowIndexes.count
  
  // 2
  if (directoryItems == nil) {
    text = "No Items"
  }
  else if(itemsSelected == 0) {
    text = "\(directoryItems!.count) items"
  }
  else {
    text = "\(itemsSelected) of \(directoryItems!.count) selected"
  }
  // 3
  statusLabel.stringValue = text
}

This method updates the status label text based on the user selection.

  1. The table view property selectedRowIndexes contains the indexes of the selected rows. To know how many items are selected, it just gets the array count.
  2. Based on the number of items, this builds the informative text string.
  3. Sets the status label text.

Now, you just need to invoke this method when the user changes the table view selection. Add the following code inside the table view delegate extension:

func tableViewSelectionDidChange(_ notification: Notification) {
  updateStatus()
}

When the selection changes this method is called by the table view, and then it updates the status text.

Build and run.

selection label now configured

Try it out for yourself; select one or more files in the table view and watch the informative text change to reflect your selection.

Responding to Double-Click

In macOS, a double-click usually means the user has triggered an action and your program needs to perform it.

For instance, when you’re dealing with files you usually expect the double-clicked file to open in its default application and for a folder, you expect to see its content.

You’re going to implement double-click responses now.

Double-click notifications are not sent via the table view delegate; instead, they’re sent as an action to the table view target. But to receive those notifications in the view controller, you need to set the table view’s target and doubleAction properties.

Note: Target-action is a pattern used by most controls in Cocoa to notify events. If you’re not familiar with this pattern, you can learn about it in the Target-Action section of Apple’s Cocoa Application Competencies for macOS documentation.

Add the following code inside viewDidLoad() of the ViewController:

tableView.target = self
tableView.doubleAction = #selector(tableViewDoubleClick(_:))

This tells the table view that the view controller will become the target for its actions, and then it sets the method that will be called after a double-click.

Add the tableViewDoubleClick(_:) method implementation:

func tableViewDoubleClick(_ sender:AnyObject) {
  
  // 1
  guard tableView.selectedRow >= 0,     
      let item = directoryItems?[tableView.selectedRow] else {
    return
  }
  
  if item.isFolder {
    // 2
    self.representedObject = item.url as Any
  }
  else {
    // 3
    NSWorkspace.shared().open(item.url as URL)
  }
}

Here’s the above code broken out step-by-step:

  1. If the table view selection is empty, it does nothing and returns. Also note that a double-click on an empty area of the table view will result in an tableView.selectedRow value equal to -1.
  2. If it’s a folder, it sets the representedObject property to the item’s URL. Then the table view refreshes to show the contents of that folder.
  3. If the item is a file, it opens it in the default application by calling the NSWorkspace method openURL()

Build and run and check out your handiwork.

Double-click on any file and observe how it opens in the default application. Now choose a folder and watch how the table view refreshes and displays the content of that folder.

Whoa, wait, did you just create a DIY version of Finder? Sure looks that way!

Sorting Data

Everybody loves a good sort, and in this next section you’ll learn how to sort the table view based on the user’s selection.

One of the best features of a table is one- or two-click sorting by a specific column. One click will sort it in ascending order and a second click will sort in descending order.

Implementing this particular UI is easy because NSTableView packs most of the functionality right out of the box.

Sort descriptors are what you’ll use to handle this bit, and they are simply instances of the NSSortDescriptor class that specify the desired attribute and sort order.

After setting up descriptors, this is what happens: clicking on a column header in the table view will inform you, via the delegate, which attribute should be used, and then the user will be able sort the data.

Once you set the sort descriptors, the table view provides all the UI to handle sorting, like clickable headers, arrows and notification of which sort descriptor was selected. However, it’s your responsibility to order the data based on that information, and refresh the table view to reflect the new order.

You’ll learn how to do that right now.

Woot!

Add the following code inside viewDidLoad() to create the sort descriptors:

// 1
let descriptorName = NSSortDescriptor(key: Directory.FileOrder.Name.rawValue, ascending: true)
let descriptorDate = NSSortDescriptor(key: Directory.FileOrder.Date.rawValue, ascending: true)
let descriptorSize = NSSortDescriptor(key: Directory.FileOrder.Size.rawValue, ascending: true)

// 2
tableView.tableColumns[0].sortDescriptorPrototype = descriptorName
tableView.tableColumns[1].sortDescriptorPrototype = descriptorDate
tableView.tableColumns[2].sortDescriptorPrototype = descriptorSize

This is what this code does:

  1. Creates a sort descriptor for every column, complete with a key (Name, Date or Size), that indicates the attribute by which the file list can be ordered.
  2. Adds the sort descriptors to each column by setting its sortDescriptorPrototype property.

When the user clicks on any column header, the table view will call the data source method tableView(_:sortDescriptorsDidChange:), at which point the app should sort the data based on the supplied descriptor.

Add the following code to the data source extension:

func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
  // 1
  guard let sortDescriptor = tableView.sortDescriptors.first else {
    return
  }
  if let order = Directory.FileOrder(rawValue: sortDescriptor.key!) {
    // 2
    sortOrder = order
    sortAscending = sortDescriptor.ascending
    reloadFileList()
  }
}

This code does the following:

  1. Retrieves the first sort descriptor that corresponds to the column header clicked by the user.
  2. Assigns the sortOrder and sortAscending properties of the view controller, and then calls reloadFileList(). You set it up earlier to get a sorted array of files and tell the table view to reload the data.

Build and run.

sortable-columns

Click any header to see your table view sort data. Click again in the same header to alternate between ascending and descending order.

You’ve built a nice file viewer using a table view. Congratulations!