Search code examples
swiftcocoacolorsnsbuttonappearance

NSButton turns gray when pushed


I have a NSButton with an image. When pushed the whole cell turns gray. How to prevent this? There are several posts about this topic. But most of them are like 10 years old. The most recent one was here: NSButton background transparent after getting focus

According to this, I tried with this code:

class overviewImageButton: NSButton {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    convenience init(appearance: NSAppearance) {
        self.init(appearance: appearance)

        self.appearance = NSAppearance(named: NSAppearanceNameAqua)
    }

    override func draw(_ dirtyRect: NSRect) {

        self.image = NSImage(named: "buttonImage.png")

        super.draw(dirtyRect)

        NotificationCenter.default.addObserver(forName: windowChanged, object: nil, queue: nil) {
            notification in
            self.image = NSImage(named: "buttonImage_highlighted.png")
        }
    }
}

But it doesn´t work. The buttoncelll still turns gray when pushed. Thanks for any help!


Solution

  • A lot of this was already said by both Willeke and I'L'I, so credit goes to them.

    What Willeke said:

    Never do anything in draw() except drawing. This method can get called as often as the screen refreshes. So currently you are basically trying to add yourself as the observer of the notificationCenter really often.

    Here is what you could do: Write a setup() method and call that from each initialiser. Any one of them is called once for a button instance.

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setupButton()
    }
    
    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        self.setupButton()
    }
    
    private func setupButton() {
        self.image = NSImage(named: "buttonImage.png")
    
        NotificationCenter.default.addObserver(forName: .windowChanged, object: nil, queue: nil) {
            notification in
            self.image = NSImage(named: "buttonImage_highlighted.png")
        }
    }
    

    You do not need to add the init(frame:) initialiser here. For storyboards the init(coder:) one is sufficient. I added it anyways, because you might want to initialise the button programmatically. You use the init(frame:) method there usually. If you add convenience methods, make sure to call the setup() method there as well.

    What I'L'I said: To the important stuff: What I did to suppress the grey background on mouseDown was to simply call isHighlighted = false before calling super.draw(rect).

    override func draw(_ dirtyRect: NSRect) {
        self.isHighlighted = false
    
        super.draw(dirtyRect)
    }
    

    Bonus: I see you somewhere defined a Notification.Name. You can define all of them in a project global extension to Notification.Name like this:

    extension Notification.Name {
        static let windowChanged = Notification.Name(rawValue: "WindowChangedNotification")
    }
    

    After that you can use them everywhere like system notification names.