Hi Macho Man Randy Savage,
Your diagnosis is essentially right, and I dug deeper than I expected after reproducing it. The situation is worse than just one wrong selector — NSBrowser's Cocoa Bindings layer doesn't compose with the item-based delegate API at all, regardless of cell class.
I built a small test project (item-based NSBrowser, an NSTextFieldCell subclass installed as cellPrototype, and selectionIndexPaths bound to a property). With the binding in place, the app crashes during draw with:
*** -[NSTextFieldCell setLeaf:]: unrecognized selector sent to instance ...
The actual selector is setLeaf:, not setIsLeaf: (worth knowing if you grep for it). The stack tells the rest of the story:
0 CoreFoundation __exceptionPreprocess
1 libobjc.A.dylib objc_exception_throw
...
5 AppKit -[NSBrowserBinder browser:willDisplayCell:atRow:column:] + 156
6 AppKit -[_NSBindingAdaptor browser:willDisplayCell:atRow:column:] + 288
7 AppKit -[NSBrowser _sendDelegateWillDisplayCell:atRow:column:] + 64
When you bind selectionIndexPaths, AppKit installs an _NSBindingAdaptor — specifically an NSBrowserBinder — that interposes itself between NSBrowser and your delegate. The binder's browser:willDisplayCell:atRow:column: strictly assumes the cell is an NSBrowserCell and sends setLeaf: to it during display, then forwards the call to the wrapped delegate.
Implementing -setLeaf: as a no-op (your attempted fix) gets past that crash, but two other things break:
- The binder also expects the wrapped delegate to implement
browser:willDisplayCell:atRow:column:. If yours doesn't, you'll get a second unrecognized-selector crash on the AppDelegate as the binder forwards through. (You may have implemented it without mentioning it, which is why you saw "titles missing" rather than another crash.) - More importantly, switching to an
NSBrowserCell subclass — which I tried to verify as the alternative — also fails to draw titles. The bindings layer for NSBrowser predates the item-based API. It expects the legacy NSMatrix-based data flow where contentValueForPath supplies cell content. With the item-based delegate (browser:numberOfChildrenOfItem:, browser:objectValueForItem:, and so on) in play, the binder doesn't call those methods for cell content, and NSBrowserCell ends up with no value to draw.
So the issue isn't really "the binder hates non-NSBrowserCell cells." It's "the binder is older than the item-based API, and the two don't share a data-source model."
The cleanest fix is to drop the selectionIndexPaths binding and read selection through the delegate API instead. Wire your selection state through target/action, and call selectionIndexPath / selectionIndexPaths on the browser when you need the current value. That removes NSBrowserBinder from the call path — no setLeaf: calls, no forwarded willDisplayCell:, and your existing item-based delegate methods supply cell content normally.
If you really need bindings for some other architectural reason, the path that's likely to work is the legacy NSMatrix-based browser API with the contentValueForPath binding (and NSBrowserCell cells) — that's the configuration the bindings were originally designed for. I didn't verify this end-to-end, since most projects won't want to step that far back in API surface.
If you'd find Cocoa Bindings on NSBrowser's item-based API working with custom cells useful, please file a Feedback Report. The bindings adaptor is older than the item-based API; making it compose with the modern data flow is more of an API gap than a strict bug, and FBs from real-world cases like yours raise the priority on those gaps.