NSTokenField Tip

Here’s a small problem I ran into recently, and the solution. I don’t know if it’s a Cocoa bug, or expected behaviour and I just didn’t pick up on that from the documentation, but either way it’s worth drawing attention to.

NSTokenField is the widget you see in Apple Mail and Automator, that looks like a normal text input field, but can also include blue lozenge-shaped “tokens”. They’re quite easy to use — just drop them in like a normal text box, then implement a delegate to handle callbacks for autocompletion, getting display names, and handling token menus.

If the user types some text and then lets it autocomplete, your delegate receives tokenField:shouldAddObjects:atIndex:. This gives you the opportunity to convert the text representation into an object of your choice. Often, I’ll use a Python dict here. The Token Field doesn’t assume anything about the object — it always calls your delegate to get the token’s name for display purposes so you can just return item["name"] or whatever. This is all fine.

However, there’s also the situation where the user types some text which is not in the autocompletion list. The problem I ran into is that, in this case, there are actually two separate code paths, depending on exactly what the user does after typing the text.

If they press an autocomplete key (eg the comma key), exactly the same behaviour occurs as above, so your tokenField:shouldAddObjects:atIndex: implementation needs to handle arbitrary strings in a clean way. This is not unexpected.

However, if the user simply types the text then clicks away to another widget, Cocoa doesn’t call your delegate. Nor does it skip that text. Instead, it inserts it directly into the token list as a string and there’s nothing you can do about it.

The moral of this story is that your implementations of tokenField:displayStringForRepresentedObject: and friends, must check the type of the representedObject before doing anything else with it, because it might be NSString, whatever type you’re using.

This concludes our public service announcement.