Create view based NSTableView programmatically using Bindings in Swift
Asked Answered
M

1

6

I am working through a Cocoa in Swift book and I am stuck in a chapter on Bindings. The book uses nib files but I want to do everything programmatically (since I am joining a team that does not use nibs). The project is to create a view based table with 2 columns and the content of the table is bound to the arrangedObjects of an array controller. The array controller's content is bound to an array of Employee objects (Employee has 2 properties viz. name and salary).

I was able to create the table programmatically like below (one scroll view, one table view, 2 table columns):

let tableWidth = windowWidth! * 0.6
let tableHeight = windowHeight! * 0.8

scrollView = NSScrollView(frame: NSRect(x: windowWidth!*0.05, y: windowHeight!*0.08, width: tableWidth, height: tableHeight))

employeeTable = NSTableView(frame: NSRect(x: 0, y: 0, width: tableWidth, height: tableHeight))
employeeTable?.bind("content", toObject: (self.arrayController)!, withKeyPath: "arrangedObjects", options: nil)

nameColumn = NSTableColumn(identifier: "name column")
nameColumn?.width = tableWidth * 0.4
nameColumn?.headerCell.title = "Name"

raiseColumn = NSTableColumn(identifier: "raise column")
raiseColumn?.width = tableWidth * 0.6
raiseColumn?.headerCell.title = "Raise"

employeeTable?.addTableColumn(nameColumn!)
employeeTable?.addTableColumn(raiseColumn!)
employeeTable?.setDelegate(self)

scrollView?.documentView = employeeTable

As you can see, I have no clue if this table is Cell-based or View-based. How can I tell what my table is based on? Since the chapter is on Bindings, no delegate or data source methods are being used and I would like to do the same.

Next question: Like I said, the book uses NIBs and has ready access to the NSTableView, NSTableCellView and the NSTextField under it. First, the NSTableView's content was bound to the array controller's arrangedObjects. I was able to do this part programmatically since I created the tableView object myself in code. The book then binds the NSTextField's value to the NSTableCellView's objectValue.name (name is one of the Employee object's properties). How do I do this part since I did not create these NSTableCellView and NSTextField objects in my code? Is there a way to access them (assuming my table is even View-based)?

Mycenae answered 4/1, 2016 at 1:36 Comment(2)
As far as I know, the cell vs. view determination is made in how you implement the delegate and datasource methods. As long as you implement the delegate method tableView:viewForTableColumn:row:--and you must-- you will be providing an NSTableCellView. So you get a view-based table. And even if it's possible to avoid implementing this delegate method and doing everything in IB, you have to implement this method as soon as you have more than one cellView or rowView type. So this, at least, might be in the book you're using if you proceed farther, nibs or not.Existence
Thank you. I implemented that delegate method and made it work. I had to subclass NSTableCellView and create a new property for the text field (because I could not use the textField that NSTableCellView is supposed to come with by default).Mycenae
M
5

I am answering my own question. Note that I am a beginner and do not know if this is the right way to do things. As noted by user stevesilva in the comments of the above question, I had to implement the delegate method tableView:viewForTableColumn:row: to ensure that the table is view based. Within the delegate method I tried to create a NSTableCellView and bind the textField property but this did not work. I had to subclass NSTableCellView, create a new text field property and then bind that property. This is how my delegate eventually looked.

func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {

    let frameRect = NSRect(x: 0, y: 0, width: tableColumn!.width, height: 20)

    let tableCellView = MyTableCellView(frame: frameRect)

    if tableColumn?.identifier == "name column" {
        tableCellView.aTextField?.bind("value", toObject: tableCellView, withKeyPath: "objectValue.name", options: nil)
    } else if tableColumn?.identifier == "raise column" {
        tableCellView.aTextField?.bind("value", toObject: tableCellView, withKeyPath: "objectValue.raise", options: nil)
    }

    return tableCellView
}

And this is my subclassed NSTableCellView:

class MyTableCellView: NSTableCellView {

    var aTextField: NSTextField?

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        aTextField = NSTextField(frame: frameRect)
        aTextField?.drawsBackground = false
        aTextField?.bordered = false
        self.addSubview(aTextField!)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
Mycenae answered 4/1, 2016 at 7:21 Comment(4)
@user3648895 Cell based table views use NSCell as a base class. NSTableCellView is subclassed from NSView, which makes the table view view-based.Shilling
@KevinLow What is the purpose of using NSTableCellView then, instead of using an NSTextField ?Jill
If all you are displaying is text, then you most likely do want to use an NSTextField for performance and memory reasons. NSTableCellView is mainly for more complicated setups where you might have multiple subviews. Take Apple Mail's rich message list. It shows sender, subject, message, flag, date, unread, junk, attachments, all in one row view. NSCells are like stamps in that they were completely drawn, so it was more difficult to put different kinds of content in one cell. Much better performance, though.Shilling
'NSTableCellView' has textField property as weak. So you have to create your own textfield and assign to its property, otherwise it gets deallocated almost immediately.Tiller

© 2022 - 2024 — McMap. All rights reserved.