awakeFromNib
method, do [objController setContent: self];
awakeFromNib
helps prevent a retain cycle which can lead to memory leaks.Put this in a convenient place:
+ (void) initialize { [NSTextFieldCell setDefaultPlaceholder: @"" forMarker: NSNotApplicableMarker withBinding: NSValueBinding]; } // initialize
- (void) observeValueForKeyPath: (NSString *) keyPath ofObject: (id) object change: (NSDictionary *) change context: (void *) contextThe
NSKeyValueChangeKindKey
key in the dictionary tells you if the change was an insertion
(NSKeyValueMinusSetMutation
) or a deletion (NSKeyValueIntersectSetMutation
)
If it is an insertion, the NSKeyValueChangeIndexesKey
is an index set that contains the index of the inserted object. You can query the collection for the object at that index to get the new object.
if it a deletion, the NSKeyValueChangeIndexesKey
tells you the index where the object was deleted from, and the NSKeyValueChangeOldKey
contains an NSArray of objects which were removed, in case you want to hang on to it, or use it to clean out some of your data structures.
valueForUndefinedKey:
:
- (id) valueForUndefinedKey: (NSString *) key { id value; value = [self lookUpValueUsingSomeOtherMechanism: key]; if (value == nil) { value = [super valueForUndefinedKey: key]; } return (value); } // valueForUndefinedKeySome handy uses for this is using the user defaults for storing values (you can use the key directly to
[NSUserDefaults stringForKey:]
, or use it to query the contents of an NSDictionary
The counterpart for this is
- (void) setValue: (id) value forUndefinedKey: (NSString *) key
, which you can use to stash stuff into user prefs or a dictionary.
In the header file
#import <Cocoa/Cocoa.h> @interface BWSearchArrayController : NSArrayController { NSString *searchString; } - (IBAction) search: (id) sender; @end // BWSearchArrayControllerand then in the implementation:
// returns an array containing the content of the objects arranged // with the user's critera entered into the search box thingie - (NSArray *) arrangeObjects: (NSArray *) objects { // result of the filtering NSArray *returnObjects = objects; // if there is a search string, use it to compare with the // search field string if (searchString != nil) { // where to store the filtered NSMutableArray *filteredObjects; filteredObjects = [NSMutableArray arrayWithCapacity: [objects count]]; // walk the enumerator NSEnumerator *enumerator = [objects objectEnumerator]; id item; // actully BWFileEntries while (item = [enumerator nextObject]) { // get the filename from the entry NSString *filename; filename = [item valueForKeyPath: @"fileName"]; // see if the file name matches the search string NSRange range; range = [filename rangeOfString: searchString options: NSCaseInsensitiveSearch]; // found the search string in the file name, add it to // the result set if (range.location != NSNotFound) { [filteredObjects addObject: item]; } } returnObjects = filteredObjects; } // have the superclass arrange them too, to pick up NSTableView sorting return ([super arrangeObjects:returnObjects]); } // arrangeObjectsand then to set the search string:
- (void) setSearchString: (NSString *) string { [searchString release]; if ([string length] == 0) { searchString = nil; } else { searchString = [string copy]; } } // setSearchString - (void) search: (id) sender { [self setSearchString: [sender stringValue]]; [self rearrangeObjects]; } // search
observeValueForKeyPath:ofObject:change:context:
, (or you do override it but don't handle your callbacks) you'll generally get an exception.
This trains you to do it right and never call super for the callbacks you're handling, or you'll get an exception.
Except
if you've added KVO to a subclass of NSArrayController
(and possibly all controller types), and don't call super's observeValueForKeyPath:ofObject:change:context:
, bindings won't work at all, with no warning/notice/nothing. (Courtesy of Duncan Wilcox)
[imageView bind: @"valuePath" toObject: imagesController withKeyPath: @"selection.fullPath" options: nil];In Interface Builder, "Bind To" corresponds to
imagesController
, "Controller Key" would be selection
, and "Model Key Path would be fullPath
.
Use
[imageView unbind: @"valuePath"];to remove a binding.
- (NSArray *) layers { return (layers); } // layers - (void) insertObject: (id) obj inLayersAtIndex: (unsigned) index { [layers insertObject: obj atIndex: index]; } // insertObjectInLayers - (void) removeObjectFromLayersAtIndex: (unsigned) index { [layers removeObjectAtIndex: index]; } // removeObjectFromLayersAtIndex
In Interface Builder, drag over the NSSearchField, and bind the predicate
like this:
what contains[c] $value
(assuming the attribute you're filtering is called what
)
[searchArrayController addObserver: self forKeyPath: @"selectionIndexes" options: NSKeyValueObservingOptionNew context: NULL];This makes
self
an observer of the searchArrayController
. We'll get notified when the selectionIndexes
value changes, and we'll be notified with the new value.
When the notification happens, this method (invoked against the self
used earlier) is invoked:
- (void) observeValueForKeyPath: (NSString *) keyPath ofObject: (id) object change: (NSDictionary *) change context: (void *) context { } // observeValueForKeyPathand you can poke around the arguments to see wha'happened.
- (void) setDefaultPrefs { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; [dictionary setObject: NSLocalizedString(@"BorkDown", nil) forKey: @"menuTitle"]; [dictionary setObject: NSLocalizedString(@"Time has run out.", nil) forKey: @"alertWindowText"]; // and any other preferences you might have [[NSUserDefaults standardUserDefaults] registerDefaults: dictionary]; } // setDefaultPrefs
First, in the +initialize for the class that's going to be observed:
+ (void) initialize { NSArray *keys; keys = [NSArray arrayWithObjects: @"showMajorLines", @"minorWeight", @"minorColor", @"minorWeightIndex", @"minorColorIndex", @"majorWeightIndex", @"majorColorIndex", nil]; [BWGridAttributes setKeys: keys triggerChangeNotificationsForDependentKey: @"gridAttributeChange"]; } // initializeSo now when "showMajorLines" changes, "gridAttributeChange" will also be observed. KVO requires there actually must exist a gridAttributeChange method (or ivar I presume) before it'll do the notification to observing objects, so there needs to be a do-nothing method:
- (BOOL) gridAttributeChange { return (YES); } // gridAttributeChangeSo now the view can do this:
- (void) setGridAttributes: (BWGridAttributes *) a { [attributes removeObserver: self forKeyPath: @"gridAttributeChange"]; [a retain]; [attributes release]; attributes = a; [a addObserver: self forKeyPath: @"gridAttributeChange" options: NSKeyValueObservingOptionNew context: NULL]; } // setGridAttributesAnd will get updated whenever an individual attribute changes.
setValue:forKeyPath:
to stick the value back into the
bound object.
The bindings and path are ivars:
id selectionRectBinding; NSString *selectionRectKeyPath;In
bind:toObject:withKeyPath:options:
hang on to the binding
object and the key path and set up observing:
// hold on to the binding info selectionRectBinding = observableObject; selectionRectKeyPath = [observableKeyPath copy]; // connect KVO [valuePathBinding addObserver: self forKeyPath: selectionRectKeyPath options: nil context: NULL]; // new binding, needs to redraw [self setNeedsDisplay: YES];And in the mouseUp: handler, set the value back into the bound object:
// figure out the selection rectangle NSRect selectionRect = [self normalizedSelectionRect]; // wrap in a value and tell the bound object the new value NSValue *value; value = [NSValue valueWithRect: selectionRect]; [selectionRectBinding setValue: value forKeyPath: selectionRectKeyPath];
- (oneway void) release; { // special case when count is 3, we are being retained twice by the object controller... if ( [self retainCount] == 3 ) { [super release]; [filesOwnerProxy setContent:nil]; return; } [super release]; }
selection
, using @count
.observeValueForKeyPath:
method. Register your observer like this:
[searchArrayController addObserver: self forKeyPath: @"selectionIndexes" options: NSKeyValueObservingOptionNew context: NULL];So now self's observeValue method will get invoked when
selectionIndexes
changes in the array controller.