Search code examples
swiftcocoacocoa-bindings

Swift: Cocoa binding value to a computed property does not work


I am learning KVC and binding. Currently, I am trying to bind an NSTextField to a computed property colorWallStr. I have bound the sliders' value to the corresponding color variable and also bound the value of the label to the computed property.

However, the content of the label does not change when I move the slide.

// Inside MainWindowController
dynamic var colorRed: CGFloat = 1.0
dynamic var colorGreen: CGFloat = 1.0
dynamic var colorBlue: CGFloat = 0.0

dynamic var colorWallStr: String {
    get {
        return "R: \(colorRed) G: \(colorGreen) B: \(colorBlue)"
    }
}

enter image description here enter image description here

It is working fine when I bond the label to the color variable directly.

enter image description here

Thanks @vadian's answer. Now I can update the label using property's didSet to trigger update label method (see below).

dynamic var colorBlue: CGFloat = 0.0 {
    didSet {
        updateLabel()
    }
}

func updateLabel() {
    colorWall = "R: \(colorRed) G: \(colorGreen) B: \(colorBlue)"
}

If properties used in string interpolation don't update the enclosing computed property, then why does the following code snippet does not work?

dynamic var colorWall: String {
    get {
        let red = colorRed
        let green = colorGreen
        let blue = colorBlue
        return "R: \(red) G: \(green) B: \(blue)"
    }
}

Solution

  • The Key-Value Observering API lets you handle situations like this by allowing you to register dependent keys. Here's how the documentation introduces the subject:

    There are many situations in which the value of one property depends on that of one or more other attributes in another object. If the value of one attribute changes, then the value of the derived property should also be flagged for change.

    In this situation the value of colorWallString depends on the value of your three color variables, so all you need to do is implement a class method that makes this clear:

    // It's crucial that you get the signature of this method correct, 
    // otherwise it'll just be ignored.
    class func keyPathsForValuesAffectingColorWallStr() -> Set<NSObject> {
        return Set<NSObject>(arrayLiteral: "colorRed", "colorBlue", "colorGreen")
    }
    

    As noted in the code snippet, the format of the method you use to flag up dependent keys is crucial; you can (and should) read the relevant documentation here.