IOS developer based in barcelona.

Create NSTableView programatically without Storyboards

I will share with you my experience creating some views in Slackfari. Slackfari is one of my side projects, a new Safari Extension to send messages to your Slack team. I learned a lot, about macOS especially about NSTableView and NSCollectionView. I have a demo project on Github take a look!

I'm gonna upload Slackfari to the store, but the project is fully open source, you can take a look at Slackfari.

NSTableView

At first, I used to create views with storyboards,  but then, I started working in a very big team. We create all the views programatically, so I decided to create all the views programatically (there are only a 3 or 4) IMHO I think is clearer for developers if they want to add new features, instead of creating new constraints in storyboards.

First of all, if you want to create a NSTableView programatically you need the next two variables

var scrollViewTableView = NSScrollView()
    
var tableView: NSTableView = {
    let table = NSTableView(frame: .zero)
    table.rowSizeStyle = .large
    table.backgroundColor = .clear
       
    let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "column"))
    table.headerView = nil
    column.width = 1
    table.addTableColumn(column)
    return table
 }()
 

You will set the NSScrollView .documentView property to the tableView, and then activate constraints for top, leading, trailing and bottom.

A rule of thumb learned is: you need to add a NSTableColumn, if you don't create a column, the table view won't be displayed. (also, you must add a width)

Once we have created our view with all the subviews, we need to load this mainView from the NSViewController.  So, we create our NSViewController with the next lines of code:

class TableViewController: NSViewController {
    var mainView: TableView { return self.view as! TableView }
    fileprivate var adapter: AdapterTableView?
    
    // MARK: View Controller
    
    override func loadView() {
        let rect = NSRect(x: 0, y: 0, width: 200, height: 200)
        view = TableView(frame: rect)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureTableView()
    }
    
    func configureTableView() {
        adapter = AdapterTableView(tableView: mainView.tableView)
        let users = [User(name: "Alberto"),
                     User(name: "Felipe"),
                     User(name: "Aaron"),
                     User(name: "Laura"),
                     User(name: "Giuseppe")]
        adapter?.add(items: users)
    }
}

The most important part is the magic behind the loadView function, we don't use nibs, xibs or storyboards, so we need to instantiate our view here. More information can be found here

In my little project there are another important class AdapterTableView, it only handles the new data to display in our NSTableView

class AdapterTableView: NSObject {
    fileprivate static let column = "column"
    fileprivate static let heightOfRow: CGFloat = 26
    
    fileprivate var items: [User] = [User]() {
        didSet {
            tableView.reloadData()
        }
    }
    
    private var tableView: NSTableView
    
    init(tableView: NSTableView) {
        self.tableView = tableView
        super.init()
        self.tableView.dataSource = self
        self.tableView.delegate = self
    }
        
    func add(items: [User]) {
        self.items += items
    }
}

extension AdapterTableView: NSTableViewDelegate, NSTableViewDataSource {
    func numberOfRows(in tableView: NSTableView) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        guard (tableColumn?.identifier)!.rawValue == AdapterTableView.column else { fatalError("AdapterTableView identifier not found") }
        
        let name = items[row].name
        let view = NSTextField(string: name)
        view.isEditable = false
        view.isBordered = false
        view.backgroundColor = .clear
        return view
    }
    
    func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
        return AdapterTableView.heightOfRow
    }
    
    func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
        return true
    }
}

Conclusion

I really enjoy learning new things in macOS. The API is very similar to iOS, but it have differences. For example is you want to set the NSView background color, first of all you need to set the wantsLayer property to true and then change the background color view.layer?.backgroundColor.

I'm all ears to know your feedback, thanks for reading! 😃

Create NSCollectionView programatically without Storyboards

Interceptors in your URLRequest