Trouble binding against transient Core Data property

  • I have a Core Data Entity called VisualizationPlugIn, and a class to go with it. This Entity declares these properties:

    NSString*     shadowFrame;
    NSRect        frame;        //  transient

    and implements the getter and setter for frame as you would expect, converting the rect to/from an NSString, and calling will/didAccess/ChangeValueForKey.

    When one of these is instantiated, it can also create an NSViewController subclass and view hierarchy that gets added to a window. When that happens, I programmatically bind the plug-in's top-level view's frame to the plug-in's frame Core Data property. This all seems to work. If the user re-positions the view hierarchy in the window, Core Data properly saves and restores that frame.

    But Undo doesn't work. If I move the plug-in's view around, I can see that the context gets dirty (the close button in the window fills in), but if I undo, the view isn't moved back, and the context doesn't get clean.

    So I thought Core Data must be undoing the shadowFrame property, and no one's getting notified. To the VisualizationPlugIn class, I added

    + (NSSet*)
    keyPathsForValuesAffectingFrame
    {
        return [NSSet setWithObject: @"shadowFrame"];
    }

    But it never gets called.

    Any idea what I should be doing instead? Thanks!

    --
    Rick
  • On Oct 10, 2010, at 18:55, Rick Mann wrote:

    > I have a Core Data Entity called VisualizationPlugIn, and a class to go with it. This Entity declares these properties:
    >
    > NSString*     shadowFrame;
    > NSRect        frame;        //  transient
    >
    > and implements the getter and setter for frame as you would expect, converting the rect to/from an NSString, and calling will/didAccess/ChangeValueForKey.
    >
    > When one of these is instantiated, it can also create an NSViewController subclass and view hierarchy that gets added to a window. When that happens, I programmatically bind the plug-in's top-level view's frame to the plug-in's frame Core Data property. This all seems to work. If the user re-positions the view hierarchy in the window, Core Data properly saves and restores that frame.
    >
    > But Undo doesn't work. If I move the plug-in's view around, I can see that the context gets dirty (the close button in the window fills in), but if I undo, the view isn't moved back, and the context doesn't get clean.
    >
    > So I thought Core Data must be undoing the shadowFrame property, and no one's getting notified. To the VisualizationPlugIn class, I added
    >
    > + (NSSet*)
    > keyPathsForValuesAffectingFrame
    > {
    > return [NSSet setWithObject: @"shadowFrame"];
    > }
    >
    > But it never gets called.

    There are some things you need to clarify here. You call the "frame" property transient, but do you mean a transient Core Data property, or just a non-Core Data property of the managed object subclass? Can you post code from the "frame" accessors that show how they relate to the "shadowFrame" property, and how the KVO mechanics are handled? Are you using the Core-Data-provided accessors (i.e. @dynamic) for "shadowFrame"?

    What does "programmatically bind" mean? Are you saying you called [view bind:@"frame" toObject:something withKeyPath:@"frame" ...], or something like that? If so, this isn't going to work, because AFAICT NSView doesn't have a "frame" binding. Can you post your code for that, too?

    Finding out why a 'keyPathsForValuesAffecting<Key>' method doesn't have the desired effect can be an extremely frustrating debugging exercise. Note that one reason why it won't get called is if nothing's actually observing the "frame" property.
  • On Oct 10, 2010, at 21:37:37, Quincey Morris wrote:

    > There are some things you need to clarify here. You call the "frame" property transient, but do you mean a transient Core Data property, or just a non-Core Data property of the managed object subclass? Can you post code from the "frame" accessors that show how they relate to the "shadowFrame" property, and how the KVO mechanics are handled? Are you using the Core-Data-provided accessors (i.e. @dynamic) for "shadowFrame"?

    @dynamic shadowFrame;

    "frame" is a transient Core Data property of Undefined Type, but it's an NSRect in the class file. Its implementation:

    - (NSRect)
    frame
    {
    [self willAccessValueForKey:@"frame"];
    NSString* v = self.shadowFrame;
    [self didAccessValueForKey:@"frame"];
    NSRect nsR = NSRectFromString(v);
    return nsR;
    }

    - (void)
    setFrame: (NSRect) inVal
    {
    NSString* v = NSStringFromRect(inVal);
    [self willChangeValueForKey:@"frame"];
    self.shadowFrame = v;
    [self didChangeValueForKey:@"frame"];
    }

    > What does "programmatically bind" mean? Are you saying you called [view bind:@"frame" toObject:something withKeyPath:@"frame" ...], or something like that? If so, this isn't going to work, because AFAICT NSView doesn't have a "frame" binding. Can you post your code for that, too?

    VisualizationPlugIn* vp = ...;
    NSView* view = vp.displayViewController.view;
    [vp bind: @"frame" toObject: view withKeyPath: @"frame" options: nil];

    I may not really fully understand bindings, in the sense that I don't know what you mean about NSView not having a "frame" binding. The docs imply that you can only call -bind:... on a binding that you've called -exposeBinding:... on, but someone at Apple told me that you only need to call -exposeBinding:... for IB's sake.

    > Finding out why a 'keyPathsForValuesAffecting<Key>' method doesn't have the desired effect can be an extremely frustrating debugging exercise. Note that one reason why it won't get called is if nothing's actually observing the "frame" property.

    Before I did this, it didn't save the view's frame. After doing this, it does, so something's working. I do have to set the frame once on load, though, otherwise it doesn't put the view in the right place (it just puts it in the default location). That, coupled with the undo behavior, makes me think the binding is "one-way," in that changing the view's frame does change the frame in the managed object, but a change in the managed object's underlying shadowFrame doesn't seem to make its way back to the view.

    --
    Rick
  • On Oct 10, 2010, at 21:52, Rick Mann wrote:

    > @dynamic shadowFrame;
    >
    > "frame" is a transient Core Data property of Undefined Type, but it's an NSRect in the class file. Its implementation:
    >
    > - (NSRect)
    > frame
    > {
    > [self willAccessValueForKey:@"frame"];
    > NSString* v = self.shadowFrame;
    > [self didAccessValueForKey:@"frame"];
    > NSRect nsR = NSRectFromString(v);
    > return nsR;
    > }

    The Core Data programming guide is a bit hard to follow on this subject (because it talks about the difficult cases and not the easy cases), but I *think* this is an unnecessary approach. Just don't put "frame" in the Core Data model at all, and let it be a regular property. The implementation is the same as you've got, except that you don't need the willAccess/didAccess/wiilChange/didChange calls. OTOH, I *think* what you did should work fine. I'm pretty sure this is nothing to do with your problem.

    On Oct 10, 2010, at 21:52, Rick Mann wrote:

    > VisualizationPlugIn* vp = ...;
    > NSView* view = vp.displayViewController.view;
    > [vp bind: @"frame" toObject: view withKeyPath: @"frame" options: nil];
    >
    > I may not really fully understand bindings, in the sense that I don't know what you mean about NSView not having a "frame" binding. The docs imply that you can only call -bind:... on a binding that you've called -exposeBinding:... on, but someone at Apple told me that you only need to call -exposeBinding:... for IB's sake.

    It's more complicated than that. 'exposeBinding...' is necessary only for IB's sake, so it's basically irrelevant to this discussion.

    However, a binding -- a real, honest-to-goodness Cocoa binding -- needs support code in the class that defines the binding. This support code essentially makes sure that the bound object are updated in both directions. For *one* of the directions (from the bound object to the bound-to object), the standard implementation of 'bind:toObject:withKeyPath:options:' in NSObject is normally perfectly adequate. The other direction is custom code in the class providing the binding.

    So, when I said NSView doesn't have a "frame" binding, I meant it doesn't have this custom code for a binding named "frame". (It does have the code for a binding named "toolTip", for example.)

    Under these circumstances, when you call [object1 bind:@"xyz" toObject: object2 withKeyPath:@"abc" options:...], all you get is object1's KVC-compliant property "xyz" following object2's "abc" property, but not vice versa. In that sense, this is not about bindings at all -- 'bind...' serves merely as a convenient way to set up a uni-directional KVO observance.

    In your case, you reversed the order of the objects -- you "bound" the plugin's "frame" property to the view's "frame" property. In other words, you told your plugin object to KVO observe the view, and nothing more. That's why moving the view changes the model. But since it's uni-directional, changing the model (e.g. via undo) doesn't change the view.

    If this sounds confusing, it is. The term "binding" is essentially ambiguous is very non-trivial way. That's why I strongly object to using 'bind...' for mere KVO observance -- it obscures the real operation of the bindings mechanism.

    In summary, you've:

    1. "bound" in the wrong direction -- you should have attempted to bind from the view to the data model (plugin)

    2. failed to establish a "frame" binding because none of the classes involve has a "frame" binding (just a "frame" property, which is insufficient for your purposes)

    3. set up a one-way KVO observance that provides precisely half the functionality you want

    One way or another, you're going to have to come up with code to handle both directions of frame change: model-to-view  and view-to-model. One approach is to use KVO observance (via [view bind:@'frame" toObject:vp withKeyPath:@"frame" options:0] if you really want to -- note the change of direction) for one direction, and have a controller object (such as a view controller or window controller) observe frame-change notifications from the view and update the model property directly.

    Or something like that.
  • On Oct 10, 2010, at 22:31:47, Quincey Morris wrote:

    > One way or another, you're going to have to come up with code to handle both directions of frame change: model-to-view  and view-to-model. One approach is to use KVO observance (via [view bind:@'frame" toObject:vp withKeyPath:@"frame" options:0] if you really want to -- note the change of direction) for one direction, and have a controller object (such as a view controller or window controller) observe frame-change notifications from the view and update the model property directly.

    Thanks for the attempt to explain this all to me. I wish I were smart enough to understand it. I had assumed that -bind:... was short for "establish KVO in two directions," but clearly that's not the case. I thought that if the two properties were KVC/KVO-compliant, things would just work.

    Since each plug-in must subclass NSViewController in order to provide a view to display, I can provide a custom NSViewController subclass for them to use, and put whatever code is necessary into that. But it's not clear to me exactly what I would do, then.

    The SimpleBindingsAdoption_02 sample app does this:

        NSObjectController *controller = [[[NSObjectController alloc] init] autorelease];
        [controller bind:@"contentObject" toObject:self withKeyPath:@"track" options:nil];

        [textField bind:@"value" toObject:controller withKeyPath:@"selection.volume" options:nil];
        [slider bind:@"value" toObject:controller withKeyPath:@"selection.volume" options:nil];

    NSViewController has a "representedObject" property that it claims can be used for binding, I wonder if I can point that at the plug-in instance and bind through that property. Guess it's worth a try...

    [view bind: @"frame" toObject: controller withKeyPath: @"representedObject.frame" options: nil];

    And, nope. Now the value in the model puts the view in the specified place, but changing the view's frame does not propagate the change to the model. Does that mean that -[NSView frame] is not KVC-compliant, or that I still need custom code somewhere to create the binding?

    --
    Rick
  • On Oct 11, 2010, at 3:41 PM, Rick Mann wrote:

    > [view bind: @"frame" toObject: controller withKeyPath: @"representedObject.frame" options: nil];
    >
    > And, nope. Now the value in the model puts the view in the specified place, but changing the view's frame does not propagate the change to the model. Does that mean that -[NSView frame] is not KVC-compliant, or that I still need custom code somewhere to create the binding?

    NSView's "frame" is not an observable property, AFAIK. Generally the rule is, if the docs don't say it is observable, don't assume that it is or rely on future behavior if it acts as if it does.

    Instead, you can register for NSViewFrameDidChangeNotification (see also -setPostsFrameChangedNotifications:).

    HTH,

    Keary Suska
    Esoteritech, Inc.
    "Demystifying technology for your home or business"
  • On Oct 11, 2010, at 14:41, Rick Mann wrote:

    > [view bind: @"frame" toObject: controller withKeyPath: @"representedObject.frame" options: nil];
    >
    > And, nope. Now the value in the model puts the view in the specified place, but changing the view's frame does not propagate the change to the model. Does that mean that -[NSView frame] is not KVC-compliant, or that I still need custom code somewhere to create the binding?

    Actually, what you did, more or less, was to demonstrate the uni-directional nature of the -[NSObject bind...] when used in isolation. In your original code, half of it worked. In this code, the other half works. :)

    > Since each plug-in must subclass NSViewController in order to provide a view to display, I can provide a custom NSViewController subclass for them to use, and put whatever code is necessary into that. But it's not clear to me exactly what I would do, then.

    How about something like this:

    Give your view controller two properties: "modelFrame" and "viewFrame". Use KVO to derive these properties from the 2 "frame" properties. (You should be able to use 'keyPathsForValuesAffecting<Key>' based on a key path, or you can even use the 'bind...' method if you wish, or you can do it manually via 'addObserver:...'.

    Write custom setters for the view controller properties that transfer the frame rect between the view and the model as necessary. You'll have to deal with one level of possible recursion (the setter for one property will probably trigger the other), but you shouldn't have to worry about infinite recursion, because KVO prevents it in a case like this.

    That should be all you need to do.

    Note: Keary just pointed out in a separate response that you shouldn't rely on NSView's "frame" property being KVO compliant (though I think it is). As he suggests, you can observe the NSView frame-changed notification instead, which is just as easy as a KVO notification.
  • On Oct 10, 2010, at 22:31:47, Quincey Morris wrote:

    > One way or another, you're going to have to come up with code to handle both directions of frame change: model-to-view  and view-to-model. One approach is to use KVO observance (via [view bind:@'frame" toObject:vp withKeyPath:@"frame" options:0] if you really want to -- note the change of direction) for one direction, and have a controller object (such as a view controller or window controller) observe frame-change notifications from the view and update the model property directly.

    I'm still struggling to understand how this stuff works. I mean, given a pair of KVO-compliant properties, I don't see why Cocoa can't handle it all for me with a single call to -bind:…. But since I want to get this to actually work, I'll try to do it their way. Problem is, I just don't get how that's supposed to be.

    I'm looking at the BindingsJoystick example. It subclasses NSView, and implements a -bind:… method for it:

    - (void)bind:(NSString *)bindingName
        toObject:(id)observableController
    withKeyPath:(NSString *)keyPath
        options:(NSDictionary *)options
    {
        if ([bindingName isEqualToString:@"angle"])
        {
      // observe the controller for changes -- note, pass binding identifier
      // as the context, so we get that back in observeValueForKeyPath:...
      // that way we can determine what needs to be updated.
      [observableController addObserver:self
                                 forKeyPath:keyPath
                                     options:nil
                                     context:AngleObservationContext];

      // register what controller and what keypath are
      // associated with this binding
      [self setObservedObjectForAngle:observableController];
      [self setObservedKeyPathForAngle:keyPath];
      // options
      angleValueTransformerName = [[options objectForKey:NSValueTransformerNameBindingOption] copy];
      allowsMultipleSelectionForAngle = NO;
      if ([[options objectForKey:NSAllowsEditingMultipleValuesSelectionBindingOption] boolValue])
      {
      allowsMultipleSelectionForAngle = YES;
      }
        }

        if ([bindingName isEqualToString:@"offset"])
        {
      [observableController addObserver:self
                                 forKeyPath:keyPath
                                     options:nil
                                     context:OffsetObservationContext];

      [self setObservedObjectForOffset:observableController];
      [self setObservedKeyPathForOffset:keyPath];
      allowsMultipleSelectionForOffset = NO;
      if ([[options objectForKey:NSAllowsEditingMultipleValuesSelectionBindingOption] boolValue])
      {
      allowsMultipleSelectionForOffset = YES;
      }
        }

    [super bind:bindingName
         toObject:observableController
    withKeyPath:keyPath
      options:options];

    [self setNeedsDisplay:YES];
    }

    It then calls -bind:… on the  joystick view like this:

    [joystick bind:@"angle"
             toObject:arrayController
         withKeyPath:@"selection.angle"
             options:options];

    In the joystick's bind, it explicitly sets up to observe changes in the angle on the arrayController's selection. But I don't get why they need to do that. Here's why:

    Analogously, I was calling -bind:… on my PlugIn model object. According to you

    > In your case, you reversed the order of the objects -- you "bound" the plugin's "frame" property to the view's "frame" property. In other words, you told your plugin object to KVO observe the view, and nothing more. That's why moving the view changes the model. But since it's uni-directional, changing the model (e.g. via undo) doesn't change the view.

    And I definitely had this behavior; the model noticed changes in the view. In other words, sending -bind:… makes the receiver observe the thing. If calling -bind:… on the joystick makes it observe the thing, then why does it explicitly observe it in its implementation, anyway?

    So, I've been preparing to implement -bind:… on my NSViewController subclass, rather than subclass NSView. I should be able to do this on either side of things, right? But I feel like it's redundant to observe the thing passed to you in bind, because I've demonstrated that that happens automatically (when you call -bind:…).

    What all the examples seem to have in common is an NSObjectController (or subclass) fronting for the model object. I don't really have that, and I'm a little reluctant to add one (it's yet another object). I have to have an NSViewController, because there are potentially complex views associated with each PlugIn. I can add an NSObjectController that represents the PlugIn (the model object), but I tried this and it didn't work. It also rubs me the wrong way that NSObjectController has a notion of a "selection," which really isn't appropriate in this situation. I have a model and a view that I want bound to each other, and the view will never switch to refer to another model object.

    So, I'm still unsure and uncomfortable with how this is supposed to be done.

    And in all this, I don't see how bindings fulfills the promise of relieving me from writing glue code.

    Thanks again for your help so far!

    --
    Rick
  • On Oct 12, 2010, at 14:58, Rick Mann wrote:

    > I'm still struggling to understand how this stuff works. I mean, given a pair of KVO-compliant properties, I don't see why Cocoa can't handle it all for me with a single call to -bind:….

    Because that's not what Cocoa bindings are. You're expecting a mechanism that keeps two KVC properties (in different objects) locked in synchronization. That might be useful, but that's not what bindings are for.

    We need terminology for this, since "binding" is ambiguous. Let's say a "binding-definition" is a mechanism that allows two objects to be linked together, and that a "binding-link" is an actual connection between two specific objects. (This is like a class/instance distinction.)

    In general, a binding-definition is a named non-KVC attribute of an object ("object1"), which connects some internal state of the object to a KVC attribute of another object ("object2"), so that object1's internal state tracks the object2's attribute, in a way that's up to object1 to determine. Since object1's attribute is not KVC, it doesn't have to follow any KVC rules. Since object's property is, it does.

    Now, the BindingJoystick example is pretty much going to drive you insane if you try to understand it. I'm not sure that I do, but I believe this is what's happening:

    -- It (a NSView subclass) implements 2 binding-definitions, "angle" and "offset".

    -- It also implements 2 KVC properties, "angle" and "offset", which pretty much have nothing to do with the bindings.

    > But since I want to get this to actually work, I'll try to do it their way. Problem is, I just don't get how that's supposed to be.
    >
    > I'm looking at the BindingsJoystick example. It subclasses NSView, and implements a -bind:… method for it:
    >
    > - (void)bind:(NSString *)bindingName
    > toObject:(id)observableController
    > withKeyPath:(NSString *)keyPath
    > options:(NSDictionary *)options
    > {
    > if ([bindingName isEqualToString:@"angle"])
    > {
    > // observe the controller for changes -- note, pass binding identifier
    > // as the context, so we get that back in observeValueForKeyPath:...
    > // that way we can determine what needs to be updated.
    > [observableController addObserver:self
    > forKeyPath:keyPath
    > options:nil
    > context:AngleObservationContext];
    >
    > // register what controller and what keypath are
    > // associated with this binding
    > [self setObservedObjectForAngle:observableController];
    > [self setObservedKeyPathForAngle:keyPath];
    > // options
    > angleValueTransformerName = [[options objectForKey:NSValueTransformerNameBindingOption] copy];
    > allowsMultipleSelectionForAngle = NO;
    > if ([[options objectForKey:NSAllowsEditingMultipleValuesSelectionBindingOption] boolValue])
    > {
    > allowsMultipleSelectionForAngle = YES;
    > }
    > }
    >
    > if ([bindingName isEqualToString:@"offset"])
    > {
    > [observableController addObserver:self
    > forKeyPath:keyPath
    > options:nil
    > context:OffsetObservationContext];
    >
    > [self setObservedObjectForOffset:observableController];
    > [self setObservedKeyPathForOffset:keyPath];
    > allowsMultipleSelectionForOffset = NO;
    > if ([[options objectForKey:NSAllowsEditingMultipleValuesSelectionBindingOption] boolValue])
    > {
    > allowsMultipleSelectionForOffset = YES;
    > }
    > }
    >
    > [super bind:bindingName
    > toObject:observableController
    > withKeyPath:keyPath
    > options:options];
    >
    > [self setNeedsDisplay:YES];
    > }

    I'm fairly certain this code is wrong (although, if wrong, wrong in a way that's fairly harmless). If the binding name is (say) "angle", the view (object1) is going to register 2 observations of the controller (object2). That means an KVO notification emanating from object2 is going to notify object1 twice. If you look at the rest of the code, you'll see that this is going to result in object1's "angle" KVO property getting set twice for every model-initiated change. That's wrong, but harmless.

    Or, it's possible that this code has an actual purpose, by separating the code execution path for notifying object1 of a bindings-related change from a KVO-related change, to prevent a circularity. I don't really know.

    > It then calls -bind:… on the  joystick view like this:
    >
    > [joystick bind:@"angle"
    > toObject:arrayController
    > withKeyPath:@"selection.angle"
    > options:options];
    >
    > In the joystick's bind, it explicitly sets up to observe changes in the angle on the arrayController's selection. But I don't get why they need to do that.

    They didn't need to do that, I think. Really, the [super bind:...] should have been in an 'else' branch of a single big 'if'.

    > Here's why:
    >
    > Analogously, I was calling -bind:… on my PlugIn model object. According to you
    >
    >> In your case, you reversed the order of the objects -- you "bound" the plugin's "frame" property to the view's "frame" property. In other words, you told your plugin object to KVO observe the view, and nothing more. That's why moving the view changes the model. But since it's uni-directional, changing the model (e.g. via undo) doesn't change the view.
    >
    > And I definitely had this behavior; the model noticed changes in the view. In other words, sending -bind:… makes the receiver observe the thing. If calling -bind:… on the joystick makes it observe the thing, then why does it explicitly observe it in its implementation, anyway?

    Again, because the example code is wrong, or at least incredibly devious in a way that doesn't help understanding bindings.

    > So, I've been preparing to implement -bind:… on my NSViewController subclass, rather than subclass NSView. I should be able to do this on either side of things, right? But I feel like it's redundant to observe the thing passed to you in bind, because I've demonstrated that that happens automatically (when you call -bind:…).

    Adding a "frame" binding-definition to your view controller isn't really going to solve your problem. If you get it to work, it's going to synchronize the view controller with the data model, but that isn't going to get the view's frame synchronized. The correct place to implement the binding is in the view.

    Earlier, I suggested you avoid bindings (as such) and just set up two observations in the view controller. Code in the view controller can then route the correct frame value to the appropriate object, depending on which object the frame change came from. You could also do this in the view itself.

    > What all the examples seem to have in common is an NSObjectController (or subclass) fronting for the model object. I don't really have that, and I'm a little reluctant to add one (it's yet another object). I have to have an NSViewController, because there are potentially complex views associated with each PlugIn. I can add an NSObjectController that represents the PlugIn (the model object), but I tried this and it didn't work. It also rubs me the wrong way that NSObjectController has a notion of a "selection," which really isn't appropriate in this situation. I have a model and a view that I want bound to each other, and the view will never switch to refer to another model object.

    Yes, the NS...Controller is a red herring, because it's there in the joystick example merely to permit the joystick inspector view to switch between model objects.

    > So, I'm still unsure and uncomfortable with how this is supposed to be done.
    >
    > And in all this, I don't see how bindings fulfills the promise of relieving me from writing glue code.

    If you can write a correct binding-definition in the correct object, then you should not need any additional glue code to make things work. Of course, the binding-definition code is *itself* glue code, so you won't be entirely free of glue code.

    OK, I got all the way to the end and I forgot to say one important thing. When the first parameter to 'bind...' is *not* a binding-definition name, and *is* a KVC property name, then the implementation in -[NSObject bind...] happens to set up a one-way KVO observation. This is *nothing* to do with bindings.

    Of all the mind-boggling aspects of this subject, that one is the bogglingest, so I'll say it again. When the first parameter to 'bind...' is not a binding name, the method has nothing to do with bindings, just with plain ol' KVO.

    My advice: Forget you ever started thinking about bindings. Forget about the 'bind...' method. Go back to the basics. You have objects two classes that have a "frame" property that need to be kept in sync. Use 'addObserver:forKeyPath:...' and 'observeValueForKeyPath:...' in an object suitably placed to be an observer (e.g. your view controller, or possibly your view) of both. Write code to make sure that a change in one property is reflected as a change in the other. Forget that this thread ever existed. :)
  • On Oct 12, 2010, at 16:15:17, Quincey Morris wrote:

    > My advice: Forget you ever started thinking about bindings. Forget about the 'bind...' method. Go back to the basics. You have objects two classes that have a "frame" property that need to be kept in sync. Use 'addObserver:forKeyPath:...' and 'observeValueForKeyPath:...' in an object suitably placed to be an observer (e.g. your view controller, or possibly your view) of both. Write code to make sure that a change in one property is reflected as a change in the other. Forget that this thread ever existed. :)

    Okay, I did that. In the end, I created a notification I send at the end of my dragging operation (the user can reposition a plug-in's view on the canvas), and I use that to update the model.I KVObserve changes in the model and update the view, which handles the initial load and undo.

    Side note: I had initially KVObserved the view, as well, which worked fine, but resulted in very large undo stacks with nearly every pixel of movement recorded. I could have (and probably should have) grouped the operations between the mouse down and the mouse up of the dragging operation, but I wasn't entirely sure that there wouldn't be other operations I'd make available during a drag (I know, it's a long shot, but the thinking stems from a different app I'm working on where that's possible). It does mean, however, that other operations that move the view (for example, nudging with arrow keys) will have to be sure to send the notification as well so I may go the other way. In fact, the more I think about it, the more I think I will.

    Thanks again for your help. I still don't understand how/when to create custom bindings, especially when using -bind:…, and I'd like to for a complex custom view in a manner similar to NSTableView, but that's for a different day.

    P.S.--It seems reasonable to create a category method on NSObject, call it -link:…, that does what I had expected -bind:… to do, don't you think? Set up the KVO necessary to keep to KVO-compliant properties in sync.

    --
    Rick
previous month october 2010 next month
MTWTFSS
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Go to today