How to use NSTableView in SwiftUI
I recently started learning macOS development using SwiftUI as part of my latest project: building a macOS Jupyter frontend. While I’m loving Swift (the language) and SwiftUI (the UI framework), it’s sometimes extremely difficult to find out information that feels like it should be readily available.
The latest such case is how to use an NSTableView
in SwiftUI. SwiftUI’s new List
is great for iOS and multiplatform apps, but doesn’t seem to be designed for desktop-specific apps which can do with much more information-dense UIs.
SwiftUI has the newer Table
too, but it’s also quite limited at this stage. For example, I don’t think it’s possible to make an entire row clickable.
This left me wanting to try out the much more battle-tested NSTableView
– but as an Apple dev noob, I couldn’t get a minimal example up and running after a few hours of tinkering!
… So here’s a snippet you can copy paste.1 Keep reading below if you’d like to see how it works step-by-step.
import SwiftUI
struct TableView: NSViewRepresentable {
class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
let data = ["Apple", "Banana", "Cherry"]
func numberOfRows(in tableView: NSTableView) -> Int {
.count
data}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
(labelWithString: data[row])
NSTextField}
}
func makeCoordinator() -> Coordinator {
()
Coordinator}
func makeNSView(context: Context) -> NSTableView {
let tableView = NSTableView()
.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.addTableColumn(NSTableColumn())
tableViewreturn tableView
}
func updateNSView(_ nsView: NSTableView, context: Context) {
// Do nothing
}
}
Step-by-step:
Create your table view struct, conforming to NSViewRepresentable
. This is a standard way of using AppKit/UIKit views in your SwiftUI applications.
In makeNSView
, create the NSTableView
with a single column, and leave updateNSView
blank for now:
struct TableView: NSViewRepresentable {
func makeNSView(context: Context) -> NSTableView {
let tableView = NSTableView()
.addColumn(NSTableColumn())
tableViewreturn tableView
}
func updateNSView(_ nsView: NSTableView, context: Context) {
// Do nothing
}
}
Create a Coordinator
, subclassing:
NSTableViewDelegate
which we’ll use to customize how cells are rendered as views, andNSTableViewDataSource
to define the number of rows.
Implement makeCoordinator
, returning an instance of Coordinator
, then link it to the table view in makeNSView
:
struct TableView: NSViewRepresentable {
class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
}
func makeCoordinator() -> Coordinator {
()
Coordinator}
func makeNSView(context: Context) -> NSTableView {
let tableView = NSTableView()
.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.addColumn(NSTableColumn())
tableViewreturn tableView
}
func updateNSView(_ nsView: NSTableView, context: Context) {
// Do nothing
}
}
You still won’t see anything being rendered yet, since we still need to implement NSTableViewDelegate
and NSTableViewDataSource
methods.
For this minimal example, we’ll use a simple static array of strings defined right in the coordinator, although in practice you would probably get data from the view.
Implement NSTableViewDataSource
’s numberOfRows
and NSTableViewDelegate
’s tableView(tableView:viewFor:row)
. The former returns the length of our array. The latter returns an NSTextField
created from the corresponding row of data.
class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource {
let data = ["Apple", "Banana", "Cherry"]
func numberOfRows(in tableView: NSTableView) -> Int {
.count
data}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
(labelWithString: data[row])
NSTextField}
}
That’s it! This is the minimal implementation of an NSTableView
in SwiftUI that I could find. Let me know on Twitter, via email, or via the GitHub discussion below if you have any comments or suggestions.
Here are some next steps I have in mind:
- Get preview working (this looks helpful)
- Custom cell view
- Vertically align text
- Multiple columns
- Index column
- Image column
- Date column
- Column headers
- Make it interactive
- Do something on select
- Do something on hover
- Do something on click
- Load data dynamically
Let me know if you’d find these helpful!