Search code examples
cocoainterface-buildernstableviewnstablerowview

How to prototype NSTableRowView in Interface Builder


I have a view-based NSTableView and I'm trying to customize the appearance of certain rows.

I understand I need to implement the delegate method mentioned in the title; However I'm not sure about how to do it.

The documentation says:

You can use the delegate method tableView:rowViewForRow: to customize row views. You typically use Interface Builder to design and lay out NSTableRowView prototype rows within the table. As with prototype cells, prototype rows are retrieved programmatically at runtime. Implementation of NSTableRowView subclasses is entirely optional.

However, unlike cells, there is no NSTableRowView class in interface builder, nor is it clear how to setup a "prototype" row view.

I am trying something like this (Swift 3):

func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView?
{
    if (row % 4) == 0 {
        // .................................................. 
        // [ A ] SPECIAL ROW:

        if let rowView = tableView.make(withIdentifier: "SpecialRow", owner: self) as? NSTableRowView {

            rowView.backgroundColor = NSColor.gray()
            return rowView
        }
        else {
            return nil
        }

        // ^ Always returns nil (Because I don't know how
        // to setup the prototype in Interface Builder)
    }
    else{
        // ..................................................
        // [ B ] NORMAL ROW (No customization needed)

        return nil
    } 
}

I have similar code working for cells -i.e., -tableView:viewForTableColumn:row:.


Solution

  • OK, so the obvious (?) solution worked:

    1. On Interface Builder, drag and drop a plain-vanilla NSView into the table (it will only accept the drop in a specific column, not as a direct child of the table view).

    2. Go to the Identity Inspector for the just dropped view, and change its Class to "NSTableRowView".

    3. Because just setting the .backgroundColor property as in my code does not work, I instead used this solution and added a box view as a child, and configured that in Interface Builder. I had to setup autolayout constraints between the box and the row view, so that it would stretch to the row view's actual size at runtime.

    (Alternatively, I could have played with the wantsLayer property of the row view... )


    UPDATE: It turns out the backgroundColor property I was using in my code is defined in NSTableRowView (NSView does not have such property, unlike UIView).

    But it also gets overridden by the table view's setting (i.e., alternating rows or not), so instead I should customize it in this method:

    func tableView(_ tableView: NSTableView, didAdd rowView: NSTableRowView, forRow row: Int)
    {
        if (row % 4) == 0 {
            rowView.backgroundColor = NSColor.controlAlternatingRowBackgroundColors()[1]
        }
        else{
            rowView.backgroundColor = NSColor.clear()
        }    
    }
    

    ...after it was added (and its background color configured by the table view).

    (Incidentally, it turns out I do not need a custom row view after all. At least not to customize the background color)