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

22: Communicate with NSView inside a NSCollectionView without using bindings

Communicate with NSView inside an NSCollectionView without using bindings.

Problem: you want to be able to pass values to a Custom NSView located within an NSCollectionsView. One difficulty is that an NSCollectionView appears designed to work using Key Value Coding, NSCoder and bindings. But NSView does not expose value bindings.

Another is that NSCollectionView does not cause a new Custom NSView to be generated for each new element in the collection. Instead NSCollectionView uses the associated View objects as templates or prototypes for what it displays. For this reason the Custom view's init is not called and the awake from nib is called only once at program start. That restricts the choice of places where communications between NSView and the model object may be initiated.

Thirdly, as far as I have been able to determine the ids of the Custom View object and Model Object cannot be relied on as identifiers (but I could be wrong).

Solution: use NSNotification and a flag within the drawRect method of the custom NSView to identify the first time that drawRect is called. The call to drawRect to render the new Custom View occurs at about the same time as the model object is being created. A notification posted at this time can be picked up by a controller and its identification data passed on to the newly created model object which is then able to use it to send notifications that only its associated custom view will pick up.

In the following implementation the Custom View contains a timer which causes it to change its colour at intervals. The colour is decided using a random number generator. A slider is used to send a limiting value to the Model object which it then passes to the Custom View via a notification. That limiting value in range 0 through 1.0 is multiplied with the random number with the result that the smaller the limiting value the smaller the colour value.


Create a new project in XCode: File->New Project->Cocoa Application
Call it: C22-NSCollectionsView-random

Now create the classes: MyElem and MyRandomNumber of type NSObject, MyCustView of type NSView, and MyControl of type NSControl.
Code these as shown below and save.

// MyElem.h // C22-NSCollectionsView-random // #import <Cocoa/Cocoa.h> @interface MyElem : NSObject { float modelVal; NSInteger viewIdentifier; } @property float modelVal; @property NSInteger viewIdentifier; -(void)assignViewIdentifier:(id)pId; @end // MyElem.m // C22-NSCollectionsView-random // @import "MyElem.h" @implementation MyElem @synthesize modelVal; @synthesize viewIdentifier; - (id)init { if( !( self = [super init])) { NSLog(@"MyElem init failed"); return self; } // end if self.modelVal = 1.0; self.viewIdentifier = 0; [self addObserver:self forKeyPath:@"modelVal" options:0 context:NULL]; return self; } // end init -(void)observeValueForKeyPath:pKeyPath ofObject:pObject change:pChange context:pContext { if ( [pKeyPath isEqualToString:@"modelVal"] ) { NSString * zNotificationId = [[NSString alloc] initWithFormat:@"fred%d",self.viewIdentifier]; [[NSNotificationCenter defaultCenter] postNotificationName:zNotificationId object:self]; } // end if } // end observeValueForKeyPath - (void)assignViewIdentifier:(id)pId { self.viewIdentifier = (NSInteger)pId; } // end assignViewIdentifier @end // MyRandomNumber.h // C22-NSCollectionsView-random // #import <Cocoa/Cocoa.h> @interface MyRandomNumber : NSObject { NSInteger myRandVar; //srand(time(NULL)) } @property NSInteger myRandVar; -(float)randomValue; @end // MyRandomNumber.m // C22-NSCollectionsView-random // @import "MyRandomNumber.h" @implementation MyRandomNumber @synthesize myRandVar; - (id)init { if ((self = [super init]) == nil) { NSLog(@"MyRandomNumber init failed"); return self; } // end if self.myRandVar = (NSInteger)time(NULL); return self; } // end init -(float)randomValue { // produces value in range 0.0 to 1.0 NSInteger zRandVar = (self.myRandVar * 1103515245) + 12345; if (zRandVar < 0) { zRandVar *= -1; } // end if self.myRandVar = zRandVar; return ( (float)(self.myRandVar % 10000 ) / 10000.0); } // end randomValue @end // MyCustView.h // C22-NSCollectionsView-random // #import <Cocoa/Cocoa.h> @import "MyRandomNumber.h" @import "MyElem.h" @interface MyCustView : NSView { // IBOutlet MyElem *myElemObj; float myRed; float myGreen; float myBlue; float myAlpha; float colourLimit; NSInteger myNotificationId; BOOL myBoolFirstTime; MyRandomNumber *myRandVarObj; NSTimer *myTimer; } @property float myRed; @property float myGreen; @property float myBlue; @property float myAlpha; @property float colourLimit; @property NSInteger myNotificationId; @property BOOL myBoolFirstTime; - (float)limitedRandomValue:(float)pRandVar; - (MyRandomNumber *)myRandVarObj; - (void)setMyRandVarObj:(MyRandomNumber *)pObj; - (NSTimer *)myTimer; - (void)setMyTimer:(NSTimer *)pObj; //-(void)custViewAction; -(void)makeRandom; @end // MyCustView.m // C22-NSCollectionsView-random // @import "MyCustView.h" @implementation MyCustView @synthesize myRed; @synthesize myGreen; @synthesize myBlue; @synthesize myAlpha; @synthesize colourLimit; @synthesize myNotificationId; @synthesize myBoolFirstTime; - (id)initWithFrame:(NSRect)frame { self = [super initWithFrame:frame]; if (self) { NSLog(@"Hi from initWithFrame"); } return self; } // end initWithFrame - (void)awakeFromNib { NSLog(@"Hi from awakefromib"); } -(void)mouseUp:(NSEvent *)pTheEvent { NSLog(@"Hi from mouseUp"); } - (float)limitedRandomValue:(float)pRandVar { return self.colourLimit * pRandVar; } // end limitedRandomValue -(void)changeColour { self.myRed = [self limitedRandomValue:[[self myRandVarObj]randomValue]]; self.myGreen = [self limitedRandomValue:[[self myRandVarObj]randomValue]]; self.myBlue = [self limitedRandomValue:[[self myRandVarObj]randomValue]]; [self setNeedsDisplay:YES]; } // end changeColour -(void)makeRandom { self.myRed = [[self myRandVarObj] randomValue]; self.myGreen = [[self myRandVarObj] randomValue]; self.myBlue = [[self myRandVarObj] randomValue]; self.myAlpha = 1.0; } // end makeRandom -(void)handleNotificationFromElem:(NSNotification *)pNotification { self.colourLimit = [(MyElem *)[pNotification object] modelVal]; } // end handleNotificationFromElem - (void)drawRect:(NSRect)rect { if (myBoolFirstTime != YES) { self.myNotificationId = (NSInteger)self; self.colourLimit = 1.0; [self setMyRandVarObj:[[MyRandomNumber alloc]init] ]; [self makeRandom]; self.myBoolFirstTime = YES; [[NSNotificationCenter defaultCenter] postNotificationName:@"NoteFromCustomView" object:self]; self.myTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(changeColour) userInfo:NULL repeats:YES]; NSString * zNotificationId = [[NSString alloc] initWithFormat:@"fred%d",(NSInteger)self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotificationFromElem:) name:zNotificationId object:nil]; } // end if [[NSColor colorWithCalibratedRed:self.myRed green:self.myGreen blue:self.myBlue alpha:self.myAlpha] set]; NSRectFill( rect ); } - (MyRandomNumber *)myRandVarObj { return myRandVarObj; } // end myRandVarObj - (void)setMyRandVarObj:(MyRandomNumber *)pObj { myRandVarObj = pObj; } // end setMyRandVarObj - (NSTimer *)myTimer { return myTimer; } // end myTimer - (void)setMyTimer:(NSTimer *)pObj { myTimer = pObj; } // end setMyTimer @end // MyControl.h // C22-NSCollectionsView-random // #import <Cocoa/Cocoa.h> @import "MyElem.h" @interface MyControl : NSObject { IBOutlet NSArrayController *layerArray; MyElem * myLastElemObj; } - (IBAction)newElement:(id)pId; - (MyElem *)myLastElemObj; - (void)setMyLastElemObj:(MyElem *)pObj; @end // MyControl.m // C22-NSCollectionsView-random // @import "MyControl.h" @implementation MyControl -(id)init { if (!(self = [super init])) { NSLog(@"MyControl init failed"); return self; } // end if // make ready to receive a notification from MyCustomView [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"NoteFromCustomView" object:nil]; return self; } // end init // notification from MyCustomView - (void)handleNotification:(NSNotification *)pNotification { id zCustomViewObjId = [pNotification object]; [[self myLastElemObj] assignViewIdentifier:zCustomViewObjId]; } // end handleNotification - (IBAction)newElement:(id)pId { myLastElemObj = [[MyElem alloc] init]; [layerArray addObject:myLastElemObj]; } // end newElement - (MyElem *)myLastElemObj { return myLastElemObj; } // end myLastElemObj - (void)setMyLastElemObj:(MyElem *)pObj { myLastElemObj = pObj; } // end setMyLastElemObj @end

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

Drag an NSCollectionView from the tools palette onto the Application Window. Add the square button, name it '+'. This button will cause new entries to be created. Drag an NSArrayController onto the MainMenu.xib panel. Notice the View panel that appears together with the NSCollectionViewItem object in the MainMenu.xib panel when the NSCollectionsView is put onto the Application Window.

XCode,Interface Builder - drag an NSControllerView onto the application Window

Interface Builder: Put an NSControllerView onto the application Window

Double click on the View panel to bring it to the fore and populate it with an Custom view, an NSSlider and an NSTextField. Drop an NSNumberFormatter onto the text view. In the Inspector Identity panel set the class of the custom view to MyCustomView.

XCode,Interface Builder - Design the view panel

Interface Builder: Design the view panel

Click on the NSCollectionView in the application Window. In the Inspector Bindings panel check the 'Bind to' box and choose Array Controller. Set the Controller Key to arrangedObjects (a method of the Array Controller).

XCode,Interface Builder - Bind the NSCollectionsView to the NSArrayController

Interface Builder: Bind the NSCollectionsView to the NSArrayController

Drag an NSObject ontothe MainMenu.xib and name it MyControl in the Inspector Identity pane.

XCode,Interface Builder,create the controller object

Create the controller object

Link the square '+' button to the newElement action of MyControl.

Link the layerArray outlet of MyControl to the NSArrayController.

XCode,Interface Builder, Link the square button to MyControl and MyControl to NSArrayController

Link the square button to MyControl and MyControl to NSArrayController

Click on the NSSlider to select it. In the Bindings pane of the Inspector set its Value to bind to the Collection View Item for Model Key Path representedObject.modelVal.

Bind NSSlider to NSCollectionViewItem for Model Key Path representedObject

Bind NSSlider to NSCollectionViewItem for Model Key Path representedObject

Do exactly the same for the NSTextView.

Select the NSNumberFormatter for the text field and set its min and max values and format as shown.

XCode,Interface Builder, Set the NSNumberFormatter values

Set the NSNumberFormatter values

I think that's it. Save and run.

Click the square button to create new entries. Control the luminoscity of the views by using the sliders.

Run the application

Run: use the slider to control the luminoscity of the views

If you want to download the code

Click the Download Link to obtain C22-NSCollectionsView-random.zip file of this whole OS X 10.5 Leopard program.

Download C22-NSCollectionsView-random.zip (2.3 MB)

Useful Links

Interface Builder User Guide: Configuring a CollectionView in IB_UserGuide.pdf

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 Sketch for paintings of the genocide in Rwanda represented as a large Falla in the Passeo de las Germanias in Gandia