Example code programming Objective-C with Cocoa in Xcode and Interface Builder (Leopard)

21: Communicate with objects using bindings, KVC, KVO, addObserver and observeValueForKeyPath

Communicate with objects using bindings, Key Value Coding (KVC) and Key Value Observing (KVO) using addObserver and observeValueForKeyPath notifications.

Problem: you want to be able to bind values in your objects so that when one of these changes in one object the change is propagated to all the others. One solution is to use bindings, Key Value Coding (KVC) and Key Value Observing (KVO) using addObserver and observeValueForKeyPath notifications.

Implementation.

Create a new project in XCode: File->New Project->Cocoa Application
Call it: 021-Bindings

Now create the classes: MyNSObjectModel of type NSObject, MyCustomNSView of type NSView, and MyNSObjectControl of type NSControl.
Code these as shown below and save.

// MyNSObjectModel.h // 021-Bindings // #import <Cocoa/Cocoa.h> @interface MyNSObjectModel : NSObject { float fieldValue; } @property float fieldValue; - (IBAction)changeFieldValue:(id)pId; - (void)assignValue:(float)pVal; @end // MyNSObjectModel.m // 021-Bindings // #import "MyNSObjectModel.h" @implementation MyNSObjectModel @synthesize fieldValue; - (IBAction)changeFieldValue:(id)pId { self.fieldValue += (1.0 - self.fieldValue) * 0.3; if (self.fieldValue > 1.0) { self.fieldValue -= 1.0; } // end if } // end changeFieldValue -(void)observeValueForKeyPath:pKeyPath ofObject:pObject change:pChange context:pContext { if ( [pKeyPath isEqualToString:@"fieldValue"] ) { float zVal = [(MyNSObjectModel *)pObject fieldValue]; } // end if } // end observeValueForKeyPath - (void)assignValue:(float)pVal { self.fieldValue = pVal; } // end assignValue @end // MyCustomNSView.h // 021-Bindings // #import <Cocoa/Cocoa.h> @interface MyCustomNSView : NSView { float myGreenColour; float myChangedColour; IBOutlet id myNSControlObj; IBOutlet id myNSObjectModelObj; } @property float myGreenColour; @property float myChangedColour; @property id myNSControlObj; @property id myNSObjectModelObj; - (void)initialiseNotification; @end // MyCustomNSView.m // 021-Bindings // #import "MyCustomNSView.h" #import "MyNSObjectModel.h" #import "MyNSObjectControl.h" @implementation MyCustomNSView @synthesize myGreenColour; @synthesize myChangedColour; @synthesize myNSControlObj; @synthesize myNSObjectModelObj; - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { self.myGreenColour = 0.3; } return self; } // end initWithFrame - (void)initialiseNotification { [self addObserver:myNSControlObj forKeyPath:@"myChangedColour" options:0 context:NULL]; } // end initialiseNotification -(void)mouseDown:(NSEvent *)pTheEvent { // MyNSObjectControls obesrver will see this and change fieldValue float zValue = self.myGreenColour; [(MyNSObjectModel *)myNSObjectModelObj setFieldValue:0.0]; self.myGreenColour = zValue; } // end mouseUp -(void)mouseUp:(NSEvent *)pTheEvent { // MyNSObjectControls obesrver will see this and change fieldValue self.myChangedColour = self.myGreenColour * 0.7; } // end mouseUp -(void)observeValueForKeyPath:pKeyPath ofObject:pObject change:pChange context:pContext { if ( [pKeyPath isEqualToString:@"fieldValue"] ) { self.myNSObjectModelObj = pObject; float zVal = [(MyNSObjectModel *)pObject fieldValue]; self.myGreenColour = zVal; [self setNeedsDisplay:YES]; } // end if } // end observeValueForKeyPath - (void)drawRect:(NSRect)rect { [[NSColor colorWithCalibratedRed:1.0 green:self.myGreenColour blue:0.0 alpha:1.0] set]; NSRectFill( rect ); } @end // MyNSObjectControl.h // 021-Bindings // #import <Cocoa/Cocoa.h> @interface MyNSObjectControl : NSControl { IBOutlet id myNSObjectModel; IBOutlet id myCustomNSView; } @property id myNSObjectModel; @property id myCustomNSView; - (IBAction)startObserving:(id)pId; @end // MyNSObjectControl.m // 021-Bindings // #import "MyNSObjectControl.h" #import "MyNSObjectModel.h" #import "MyCustomNSView.h" @implementation MyNSObjectControl @synthesize myNSObjectModel; @synthesize myCustomNSView; - (IBAction)startObserving:(id)pId { [self.myNSObjectModel addObserver:self.myNSObjectModel forKeyPath:@"fieldValue" options:0 context:NULL]; [self.myNSObjectModel addObserver:self.myCustomNSView forKeyPath:@"fieldValue" options:0 context:NULL]; [(MyCustomNSView *)self.myCustomNSView initialiseNotification]; } // end startObserving -(void)observeValueForKeyPath:pKeyPath ofObject:pObject change:pChange context:pContext { if ( [pKeyPath isEqualToString:@"myChangedColour"] ) { float zVal = [(MyCustomNSView *)pObject myChangedColour]; [(MyNSObjectModel *)myNSObjectModel assignValue:zVal]; } // end if } // end observeValueForKeyPath @end

Bring up Interface Builder by double clicking on MainMenu.nib.

Drag a Custom NSView, an NSTextField, a Horizontal NSSlider, two NSButtons and three labels onto the window and name them as shown.

XCode,Interface Builder - Do the layout

Interface Builder: Do the layout

Drag an NSObjectController and two NSObjects onto the MainMenu.xib and set the objects to MyNSObjectControl and MyNSObjectModel.

XCode,Interface Builder - Do the layout

Interface Builder: Drag NSObjectController and NSObjects onto MainMenu.xib. Name the NSObjects: MyNSObjectControl and MyNSObjectModel

Select NSObjectController and in the Controller Content section of the Bindings panel of the Inspector tick the bind check box, bind to MyNSObjectModel and set the Model Key Path to self.

Interface Builder - bind Controller Content

Interface Builder: Bind Controller Content

Link MyCustomView to MyNSObjectControl.

XCode,Interface Builder - Link MyCustomView to MyNSObjectControl

Interface Builder: Link MyCustomView to MyNSObjectControl

Drop an NSNumberFormatter onto the NSTextField and set its attributes as shown.

XCode,Interface Builder,set NSNumberFormatter attributes

set NSNumberFormatter attributes

Select the NSTextField, in the bindings part of the Inspector bind the field to Object Contoller for Controller Key: selection, and ModelKeypath: fieldValue.

XCode,Interface Builder, set the binding for NSTextField

Set the binding for NSTextField

Select the NSSlider and bind it exactly like the NSTextField.

*** Very important **** When binding bind to Value and not to Min Value or Max Value.

Interface Builder, bind to Value not to min value or max value

NOTE: bind to Value and not to Min Value or Max Value

In the attributes section of the Inspector set the slider values as shown.

XCode,Interface Builder, set the attributes for NSSlider

Set the attributes for NSSlider

Link the Start Observing and the Change Model Value buttons to MyNSObjectController.

Save and Run and observe. It is a pleasing toy.

XCode,Interface Builder, Run

Run: click the Start Observing button to start. Then move the slider, click the Change Model Value button and then do a mouseDown followed by a mouseUp to see the slider leap to zero and then spring part of the way back

Pitfalls

One thing I found myself doing over and over was forgetting that when one puts an object onto the .xib panel the init does not get called. When necessary use awakeFromNib

Also remember that when you put an object onto the xib panel you don't need to do a [[Classname alloc]init] because it has already been created for you in the nib.

How then to coordinate communications between different objects, e.g. how to pass the id's of various objects to each other at start time? I can't imagine this is the correct way to do it but what works for me is to send a delayed message. e.g.
[self performSelector: @selector(setUpDataComsEtc:) withObject:self afterDelay: 2.0];
where I do all the set up in setUpDataComsEtc.

See: performSelector:withObject:afterDelay: and NSInvocation Class Reference which I have not yet used. One could also use NSTimer but that requires turning it off after it has been used.

*** Very important **** When binding bind to Value and not to Min Value or Max Value.

Interface Builder, bind to Value not to min value or max value

NOTE: bind to Value and not to Min Value or Max Value

If you want to download the code

Click the Download Link to obtain 021-Bindings.zip file of this whole OS X 10.5 Leopard program.

Download 021-Bindings.zip (2.3 MB)

Useful Links



Please send me your comments

If you include your e-mail I may reply!  

Page last modified: 18:58 Sunday 12th. May 2013

Julius Guzy

Paintings & Drawings

  • Link to painting of Star of Bethlehem flowers

animatedPaint