3
votes

I was having trouble with NSTokenFieldCell, so I proceeded to create a new project in Xcode to isolate the problem. Here is what I did:

  • Dropped a NSTableView into the main window;
  • selected the second column's text cell, and changed it's Class (via Identity Inspector) to NSTokenFieldCell;
  • implemented a minimum possible data source object, with the following code:

    - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
        return 1;
    }
    
    - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
        return @"aa, bb";
    }
    

At first it seems to work fine, but if you double-click a cell to edit, then tab and shift+tab to switch cells back and forth, eventually the application crashes with a BAD ACCESS when the token field cell receive focus.

I'm using Xcode 4.2 in Lion 10.7.2, with all the default settings that come with a Mac OS X Cocoa Application template.

2
I've been frustrated with this combination too. I don't have an answer as such but I have reproduced the issue and published the code: github.com/ioquatix/TableViewTokenCellTestioquatix

2 Answers

4
votes

Looks like a bug in Cocoa. If you turn on zombies you'll see this:

2011-10-31 00:02:43.802 tokenfieldtest[35622:307] *** -[NSTokenFieldCell respondsToSelector:]: message sent to deallocated instance 0x1da761f10

I tried setting a delegate for the table and implementing - (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row, returning a new NSTokenFieldCell every time (for just the token column), but I got the same error.

0
votes

The original solution brought a new problem.

When NSTokenFieldCell is not fully displayed in NSTableView, entering the editing state and then exiting will cause the table view to display abnormally.

So I tried repeatedly to get a better solution:

class MyTokenFieldCell: NSTokenFieldCell {
    override func fieldEditor(for controlView: NSView) -> NSTextView? {
        return nil;
    }
}

It may be that NSTableView's editor reuse mechanism for NSTokenFieldCell has problems, which caused the program to crash.

fieldEditorForView: is overwritten here, returning nil, which should cause the editor to be recreated every time when editing, avoiding reuse, and thus solves the crash problem.


The following is the original answer.

⚠️ Because the solution causes other problems, please ignore it.

I also encountered this problem. My solution is to temporarily keep the cells used by the table view.

  1. Custom NSTokenFieldCell: after each copy, temporarily save the copy.

     class MyTokenFieldCell: NSTokenFieldCell {
        static var cells = [NSUserInterfaceItemIdentifier: [MyTokenFieldCell]]()
    
        override func copy(with zone: NSZone? = nil) -> Any {
            let cell = super.copy(with: zone)
    
            guard let tokenFieldCell = cell as? MyTokenFieldCell else { return cell }
            tokenFieldCell.identifier = self.identifier
    
            guard let identifier = tokenFieldCell.identifier else { return cell }
            var cells = MyTokenFieldCell.cells[identifier] ?? []
            cells.append(tokenFieldCell)
            if cells.count > 4 {
                cells.removeFirst()
            }
            MyTokenFieldCell.cells[identifier] = cells
    
            return cell
        }
    }
    
  2. Implement the tableView(_:dataCellFor:row:) method of NSTableViewDelegate, provide MyTokenFieldCell for the table view, and set the identifier to: <columnIdentifier>:<row>

    extension ViewController: NSTableViewDelegate {
        func tableView(_ tableView: NSTableView, dataCellFor tableColumn: NSTableColumn?, row: Int) -> NSCell? {
            guard let columnIdentifier = tableColumn?.identifier, columnIdentifier.rawValue == "token" else {
                return tableColumn?.dataCell(forRow: row) as? NSCell
            }
    
            let cell = MyTokenFieldCell()
            cell.isEditable = true
            cell.identifier = .init("\(columnIdentifier.rawValue):\(row)")
            return cell
        }
    }