0

I would like to add a method with a default to the NSTableViewDataSource protocol. But when I do this, the default is always called, even when the method is defined in the actual data source. Specifically:

I have a single-column NSTableView which can adopt several different data sources at different times. In one case, I would like the data source to be able to provide not only the values displayed, but the background colors of the rows in the table. In the other cases, the table can be a single color. My idea was to first extend the NSTableViewDataSource protocol:

extension NSTableViewDataSource
  { 
    func tableView (tableView: NSTableView, colorIndexForRow row: Int) -> Int
      { 
        return 0 
      }
  }

then in the table delegate I put this:

public func tableView (tableView: NSTableView, didAddRowView rowView: NSTableRowView, forRow row: Int)
  { 
    let colorIndex = tableView.dataSource()!.tableView(tableView, colorIndexForRow: row)
    rowView.backgroundColor = rowColors[colorIndex]
  }

(rowColors is just an array of NSColor objects, six for experimental purposes.)

Finally, my experimental data source looks like this:

public func numberOfRowsInTableView (tableView: NSTableView)  -> Int
  { 
    return 100
  }

public func tableView (tableView: NSTableView, objectValueForTableColumn column: NSTableColumn?, row: Int) -> AnyObject?
  { 
    return String(format: "This is row %3i", row)   
  }

public func tableView (tableView:  NSTableView, colorIndexForRow row: Int) -> Int
  { 
    return row % 6    
  }

I expected this to produce a table in which the rows were in a rotating cycle of six colors. What I actually get is a table all in the color of rowColors[0]. The default for my colorIndexForRow method is always called, even though I provide the method in my data source. My reading of Swift documentation is that the default in a protocol extension is supposed to be called only when an actual method isn’t there. Can anyone tell me what I’m doing wrong?

Jeff J
  • 139
  • 8

2 Answers2

1

The reason is because protocol extension doesn't support class polymorphism.

You can rewrite your function as:

public func tableView (tableView: NSTableView, didAddRowView rowView: NSTableRowView, forRow row: Int)
{ 
    let colorIndex = self.tableView(tableView, colorIndexForRow: row)
    rowView.backgroundColor = rowColors[colorIndex]
}
Community
  • 1
  • 1
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • Ummm…Wow? I think you’ve answered my question, but give me a day or two to absorb the reference you gave. In the meantime, I haven’t been able to make your suggested function work, probably because I didn’t stress that I’d like to keep the table delegate separate from the data source if possible. This function is in the delegate, and self.tableView isn’t available there. I’ll think about rearranging things, but now I’m not sure extending the protocol is the best way. – Jeff J Jul 29 '16 at 13:31
  • Non-polymorphism means that when you define a function, you have to stick to this original definition. Classes that provide a new implementation for the `tableView(, colorForRowIndex:)` cannot replace that. That's why it always returned 0 -- that's how it's defined originally. – Code Different Jul 29 '16 at 17:11
  • Thanks for the help. I’ve posted a final answer that seems to work for me, but I’ve given you the green flag and up vote for setting me on track. I hope that’s all in conformance to StackOverflow protocol. – Jeff J Jul 29 '16 at 17:33
  • Thank you. Appreciate your gratitude – Code Different Jul 29 '16 at 17:57
0

I’ve decided to take a different tack: my simple one-function requirement doesn’t justify dealing with static vs dynamic dispatch issues. Instead of extending NSTableViewDataSource, I’ve defined an inherited protocol:

public  protocol        ColorTableViewDataSource: NSTableViewDataSource
  { 
    func tableView (tableView: NSTableView, colorIndexForRow row: Int) -> Int
  }

And then my delegate function tests for conformance:

public func tableView (tableView: NSTableView, didAddRowView rowView: NSTableRowView, forRow row: Int)
  {
    if  let dataSource = tableView.dataSource() as? ColorTableViewDataSource
      {
        rowView.backgroundColor = rowColors[dataSource.tableView(tableView, colorIndexForRow: row)]
      }
  }

My data source functions don’t change at all. I just declare the data source object in conformance to the new protocol when I want colored rows. It seems to do exactly what I want.

Jeff J
  • 139
  • 8