Composition Over Inheritance in NSViews

I find myself looking at an NSView subclass that actually doesn’t do much. It’s an opaque container for a text field that sets a label text. “Opaque” inasmuch as its outward appearance is being view, hiding the actual component’s complexity inside. This is just one example of an oft-repeated pattern: delegate to sub-views, using view composition instead of inheritance. This reduces the public API quite a bit by virtue of its opaque nature.

Continue reading …

Auto-Growing NSTextField

I ran into a situation with a window that is movable by its background and a text field inside it. The text field draws neither border nor background, so to the user, it looks like an input field directly on the window’s background. Much like the Spotlight search box. Even when a window’s isMovableByWindowBackground is enabled, a NSTextField captures click and drag events, and it changes the pointer to a text insertion variant (NSCursor.iBeam). All that is weird when you don’t know there’s a text field at that point. And you cannot know how large the text field is if nothing is drawn there. When the background is gone, this feels pretty weird.

Continue reading …

NSTextField usesSingleLineMode Stops Working When You Implement NSTextViewDelegate Methods

Today I learned why my NSTextField permits pasting of newline characters even though I set usesSingleLineMode properly. It’s because I made it conform to NSTextViewDelegate to cache changes. When you edit text inside of an NSTextField, you actually type inside a field editor of the window. That’s a shared NSTextView instance. Most of the hard work of an NSTextField is done by its cell, which is an NSTextCell. NSTextCells implement at least the delegate method NSTextViewDelegate.textView(_:shouldChangeTextIn:replacementText:) – and when you set usesSingleLineMode, this is actually set for the cell, not the view itself. You can use textView(_:shouldChangeTextIn:replacementText:) to sanitize input text, and I suspect that’s where the usesSingleLineMode implementation happens. If your NSTextField subclass implements this method, the NSTextCell implementation isn’t called. And since that one isn’t public (it was called “implicit protocol conformance” back in the day), you cannot delegate up in Swift because the compiler knows it isn’t there.

Continue reading …