Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to create a view-based NSTableView purely in code?

Tags:

swift

cocoa

I have successfully created a cell-based NSTableView purely in code. I would like to make the cells a little more interesting and I have read that I need to create a view-based NSTableView.

I have been tutorials like this.

The rest of my UI is entirely in code. I have been trying to do the same for this tableview without much luck.

Here is how I am defining the TableView — I need to stop registering the Nib and I am not sure how:

     let nib = NSNib(nibNamed: "TransactionCellView", bundle: NSBundle.mainBundle())
        tableOfTransactions.registerNib(nib!, forIdentifier: "TransactionCellView")

        tableOfTransactions.headerView = nil

        tableOfTransactions.setDelegate(self)
        tableOfTransactions.setDataSource(self)
        tableOfTransactions.reloadData()

Here is my stub code for each cell:

func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView?{
        var testCell = NSView()
        testCell.frame = NSRect(x: 0, y: 0, width: 300, height: 200)
        return testCell
}

Any pointers or suggestions on how to achieve this would be much appreciated!

like image 583
Richard Burton Avatar asked Nov 30 '15 06:11

Richard Burton


2 Answers

Your implementation of -tableView(_:viewForTableColumn:row:) should look something like this:

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

        var retval: NSView?
        if let spareView = tableView.makeViewWithIdentifier("CodeCreatedTableCellView",
            owner: self) as? NSTableCellView {

            // We can use an old cell - no need to do anything.
            retval = spareView

        } else {

            // Create a text field for the cell
            let textField = NSTextField()
            textField.backgroundColor = NSColor.clearColor()
            textField.translatesAutoresizingMaskIntoConstraints = false
            textField.bordered = false
            textField.controlSize = NSControlSize.SmallControlSize

            // Create a cell
            let newCell = NSTableCellView()
            newCell.identifier = "CodeCreatedTableCellView"
            newCell.addSubview(textField)
            newCell.textField = textField

            // Constrain the text field within the cell
            newCell.addConstraints(
                NSLayoutConstraint.constraintsWithVisualFormat("H:|[textField]|",
                    options: [],
                    metrics: nil,
                    views: ["textField" : textField]))

            newCell.addConstraints(
                NSLayoutConstraint.constraintsWithVisualFormat("V:|[textField]|",
                    options: [],
                    metrics: nil,
                    views: ["textField" : textField]))

            textField.bind(NSValueBinding,
                toObject: newCell,
                withKeyPath: "objectValue",
                options: nil)

            retval = newCell
        }

        return retval
}

In the case where your table contains hundreds of rows, Cocoa will attempt to reuse views that have already been created, but are no longer on screen. The first part of this snippet uses an NSTableView method to look for such a view. If none is found, you need to create one from scratch.

If you've got no reason not to, you should use an instance (or subclass) of NSTableCellView as your view. It doesn't add much to NSView, but one of its key features is that it retains a reference to the model that the view represents (set by -tableView(_:objectValueForTableColumnRow:row:)). In this example I've used this feature to set the string value of the text field using bindings.

The other thing to note is that you should give your view an identifier that matches the identifier that you gave to the NSTableColumn in which the view will sit. Doing so allows your table view to make use of the reusable-view feature discussed above.

like image 134
Paul Patterson Avatar answered Nov 03 '22 11:11

Paul Patterson


This was quite useful in my attempt to program scrolling tables without IB & Nibs. Here's my toy version, updated to Swift 4.2.

The custom cell view subclass is only there to let me see that cells actually get re-used, which they do:

    import Cocoa

    class DIYTableViewDataSource: NSObject, NSTableViewDataSource {

        func numberOfRows(in tableView: NSTableView) -> Int {
            return 25
        }
    }

    class CustomTableCellView: NSTableCellView {
        var count = 1
    }

    func createCell(_ id: NSUserInterfaceItemIdentifier) -> CustomTableCellView {
        // Create a text field for the cell
        let textField = NSTextField()
        textField.backgroundColor = NSColor.clear
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.isBordered = false

        // Create a cell
        let cell = CustomTableCellView() // Doing this to see that cells get re-used
        cell.identifier = id
        cell.addSubview(textField)
        cell.textField = textField

        // Constrain the text field within the cell
        textField.widthAnchor.constraint(equalTo: cell.widthAnchor).isActive = true
        textField.heightAnchor.constraint(equalTo: cell.heightAnchor).isActive = true

        textField.bind(NSBindingName.value, to: cell, withKeyPath: "objectValue", options: nil)

        return cell
    }

    class DIYTableViewDelegate: NSObject, NSTableViewDelegate {

         func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
             let id = tableColumn!.identifier
             var view = tableView.makeView(withIdentifier: id, owner: nil) as? CustomTableCellView
             if view == nil {
                 view = createCell(id)
             }
             view!.textField!.stringValue = "\(id.rawValue) \(row) \(view!.count)"
             view!.count += 1

             return view
         }
     }
like image 2
Ron Avatar answered Nov 03 '22 11:11

Ron