Cocoa Bindings, Custom Views, and Swift

Pounding my head against a wall, trying to 1) understand bindings properly and 2) get them to work in Swift properly.

Here is a custom view. It simply draws its rect as the color specified in its properties. The view would not update properly without the funky way I created the properties.

let RED_KEY_NAME                = "red";
let GREEN_KEY_NAME              = "green";
let BLUE_KEY_NAME               = "blue";
let ALPHA_KEY_NAME              = "alpha";

class ColorView: NSView {

    override func drawRect(dirtyRect: NSRect) {

        let theContext = NSGraphicsContext.currentContext();
        
        theContext!.saveGraphicsState()
        
        let color = NSColor(deviceRed:red,
            green:green,
            blue:blue,
            alpha:alpha)
        color.set()
        NSBezierPath.fillRect(bounds)
        
        theContext!.restoreGraphicsState()
    }
    
    dynamic var red: CGFloat {
        set {
            priv_red = newValue
            needsDisplay = true
        }
        get {
            return priv_red
        }
    }
    dynamic var green: CGFloat {
        set {
            priv_green = newValue
            needsDisplay = true
        }
        get {
            return priv_green
        }
    }
    dynamic var blue: CGFloat {
        set {
            priv_blue = newValue
            needsDisplay = true
        }
        get {
            return priv_blue
        }
    }
    dynamic var alpha: CGFloat {
        set {
            priv_alpha = newValue
            needsDisplay = true
        }
        get {
            return priv_alpha
        }
    }
    private var priv_red = CGFloat(0.0)
    private var priv_green = CGFloat(0.0)
    private var priv_blue = CGFloat(0.0)
    private var priv_alpha = CGFloat(1.0)
}

The App Delegate controls some of the initialization details:

func applicationDidFinishLaunching(aNotification: NSNotification) {
    colorView.bind("red",
            toObject: controller,
            withKeyPath: "selection.red",
            options: nil)
    colorView.bind("green",
            toObject: controller,
            withKeyPath: "selection.green",
            options: nil)
    colorView.bind("blue",
            toObject: controller,
            withKeyPath: "selection.blue",
            options: nil)
    colorView.bind("alpha",
            toObject: controller,
            withKeyPath: "selection.alpha",
            options: nil)
        
    controller.setValue(NSNumber(double: 0.0), forKeyPath: "selection.red")
    controller.setValue(NSNumber(double: 0.5), forKeyPath: "selection.green")
    controller.setValue(NSNumber(double: 0.5), forKeyPath: "selection.blue")
    controller.setValue(NSNumber(double: 1.0), forKeyPath: "selection.alpha")
}

‘controller’ is an NSObjectController that was added to Nib. This controller’s mode is ‘Class’, the Class Name is NSMutableDictionary, and ‘Prepares Content’ is checked (IOW the controller instantiates the dictionary, I think). Keys ‘red’, ‘green’, ‘blue’ and ‘alpha’ have been added.

The edit fields are bound to this controller via the Bindings editor. For example, the ‘red’ edit field is bound to ‘Controller’ with Controller Key ‘selection’, and Model Key Path ‘red’. Similar bindings are set for the green, blue, and alpha edit fields.

The primary thing I don’t currently understand is why the custom view doesn’t redraw itself when the properties of the Controller it’s bound to change. Why do I have to use Computed properties with those inelegant ‘needsDisplay = true’ lines. Is this a bug in the way Swift view objects inherited from NSView are implemented, or is this something that I don’t correctly understand?

4 thoughts on “Cocoa Bindings, Custom Views, and Swift
  1. greekgoddj says:

    Hey there, did you ever get to the bottom of this? Having the same issue, where I want to make a custom NSView that knows when the model has changed so it can refresh itself.

    Thanks

    • ekampman says:

      Hmm, that was a quite awhile ago, and I don’t remember if I did at this point. At least you can do the needsRefresh thing as a workaround…

      Congratulations, btw, you are the first commenter who wasn’t spamming!

  2. Scott says:

    Don’t bother with all that. Just put a didSet { needsDisplay = true } on each of them and get rid of the get/set entries altogether.

Leave a Reply

Your email address will not be published. Required fields are marked *