Marco83
Marco83

Reputation: 1191

Programmatically add columns to NSTableView

I'm trying to programmatically add columns to a NSTableView, part of a view I defined with IB as part of a custom view controller that I would like to reuse (and I would customize the columns for each specific use).

I'm using a data source/delegate to provide row/column values. I simplified my code to the minimum code needed to reproduce the issue.

The table shows completely wacky behaviour: I create 1000 rows, but only the first ~17 (those who fit the initial view port) are displayed, the rest, visible by scrolling down, are empty. When scrolling back up, even the initial ones are now gone. In addition, the row views are clipped.

Here's a video of the behaviour: https://www.youtube.com/watch?v=toRpIfp5A6w

For those who can't play the video, here's a screenshot half-way through the scrolling. Keep in mind that the table is supposed to have 1000 rows.

Table view visual errors

Here's my code.

AppDelegate

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        let controller = TestViewController()
        self.window.contentView?.addSubview(controller.view)
        controller.view.translatesAutoresizingMaskIntoConstraints = false
        controller.view.heightAnchor.constraint(equalTo: self.window.contentView!.heightAnchor).isActive = true
        controller.view.widthAnchor.constraint(equalTo: self.window.contentView!.widthAnchor).isActive = true
        controller.view.trailingAnchor.constraint(equalTo: self.window.contentView!.trailingAnchor).isActive = true
    }

    func applicationWillTerminate(_ aNotification: Notification) {
    }
}

View controller

import Cocoa

class TestViewController: NSViewController {

    @IBOutlet weak var tableView: NSTableView!
    private var tableSource = TestTableSource()

    override func viewDidLoad() {
        super.viewDidLoad()
        let columns = self.tableView.tableColumns
        columns.forEach {
            self.tableView.removeTableColumn($0)
        }
        let column = NSTableColumn(identifier: "c1")
        column.title = "COL1"
        self.tableView.addTableColumn(column)
        self.tableView.delegate = self.tableSource
        self.tableView.dataSource = self.tableSource
        self.tableView.reloadData()
    }
}

class TestTableSource: NSObject, NSTableViewDelegate, NSTableViewDataSource {

    let array = (0..<1000).map { "row \($0)" }

    func numberOfRows(in tableView: NSTableView) -> Int {
        return array.count
    }

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {        
        guard tableColumn?.identifier == "c1" else { fatalError() }
        let view = NSTextField(string: array[row])
        return view
    }
}

View Controller in IB

These are the NSTableView settings NSTableView settings

and

NSTableView settings 2

Any suggestion on how to address the issue is welcome!

EDIT: I forgot to switch to View-based. I uploaded the new screenshot. The problem is still there.

Upvotes: 1

Views: 3109

Answers (1)

Marco83
Marco83

Reputation: 1191

Solved: the table view delegate/data source was deallocated immediately because nothing was holding a strong reference to it.

Since the delegate is a weak property in NSTableView, any further attempt at getting the value of a cell would fail.

Long story: TestViewController was declared as a scope variable, and then its view added to the view hierarchy. But since nothing was holding on to this view controller itself, it was deallocated immediately. And since the view controller was the one holding on to the delegate/data source, this would be deallocated too.

Kudos to @biappi for helping me sort this out.

Upvotes: 1

Related Questions