Search code examples
xcodemacoscocoaappkit

Subclassing NSView Subclasses (NSTextField, NSButton, NSPopUpButton)


I've been wrestling with applying my custom styles (example) to the OSX desktop app I've been building, and I'm wondering what the paradigms/idioms are for doing this.

  • I'm using X/Nibs for laying out the components on the windows
  • I'm setting the components' class to a custom subclass of the out-of-the-box component (NSTextField, NSButton, and NSPopUpButton for example)
  • I'm overriding viewWillDraw in some, init? in others and making them all wantsLayer and setting attributes on those layers

This seems extremely clumsy at best, and some visual things are impossible to handle this way, like adding padding to an NSTextField. Am I supposed to create a NSTextFieldCell subclass and instantiate one and set it to the cell? Doing that breaks the text field altogether. I'm seeing other places that you should subclass NSButtonCell, but I can't assign that as the class of a button in the X/Nib. (@Willeke informed me in the comments that it's possible to select a control's cell, and therefore assign a custom cell class by clicking on the control again)

A lot of people are describing how to do certain changes to visual components in viewDidLoad with a reference to a component, which seems very tedious if you have button/textfield styles that apply to all in the project. It seems like every visual change I've made with subclassing has been a nightmare, and there's no pattern for what visual changes are made where for different components. Subclassing seems like the best option for any size of project if you want to make your code reusable, but am I missing something? Is there some other pattern or idiom that I'm missing?


Solution

  • I did end up subclassing NSTextFieldCell, and from this answer I derived the following for the specific problem I was facing:

    class TGTextFieldCell: NSTextFieldCell {
        override init(imageCell image: NSImage?) {
            super.init(imageCell: image)
        }
    
        override init(textCell aString: String) {
            super.init(textCell: aString)
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        override func drawingRectForBounds(theRect: NSRect) -> NSRect {
            let rectInset = NSMakeRect(theRect.origin.x + 7, theRect.origin.y + 7, theRect.size.width, theRect.size.height)
            return super.drawingRectForBounds(rectInset)
        }
    }
    

    It still seems that customizing views is much more complicated than it needs to be (moving on to customizing focus styles, for example), but that's a question to I have yet to hear a good answer.