Search code examples
swiftappkitkey-value-observingcocoa-bindingsnsimageview

Subclassing NSImageView with the binding ability


I'm trying to animate the image change in NSImageView. It is a purely academic problem, and I'm staying within the restrictions of a macOS binding framework.

Please, consider the model:

class Model: NSObject {
    /// Image object for the NSImageView
    @objc dynamic var image: NSImage?
    /// The target action for button `A`.
    /// Changes the image inside model.
    /// The change is not a KVO compliment because such a change is 'inside' the model.
    @objc func a() {
        self.image = NSImage(named: "a")
    }
    /// The target action for the button B
    /// In the window UI
    @objc func b() {
        self.image = NSImage(named: "b")
    }
}

enter image description here

It is the simplest and strait-forward binding example from the bible (What Are Cocoa Bindings?). The difference I want to achieve is to make the image change process visible to the user.

I will intercept every new image moving to the NSImageView from NSObjectController and transit it with the old one.

class AnimatedImageView: NSImageView {

    override func setValue(_ value: Any?, forKey key: String) {
        // The code execution is never through this point.
        if key == "image" {
            self.transition(value as! NSImage?)
        } else {
            super.setValue(value, forKey: key)
        }
    }

    private func transition(_ newImage:NSImage?) {
        let transition = CATransition()
        transition.duration = 1
        transition.type = .fade
        transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
        self.layer?.add(transition, forKey: nil)
        self.image = newImage
    }

}

The attempt to override the method setValue(_:forKey:) was not successful. Despite this, I know that the method bind(...) is firing well, and the application itself is working well, except for animation.

What I missed?

Please, find attached the project source is there.


Solution

  • The value binder calls the Objective-C method setObjectValue:. In Swift, override the objectValue property.