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 *) context
The 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);
} // valueForUndefinedKey
Some 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 // BWSearchArrayController
and 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]);
} // arrangeObjects
and 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
{
} // observeValueForKeyPath
and 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];
} // setDefaultPrefsFirst, 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"];
} // initialize
So 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);
} // gridAttributeChange
So 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];
} // setGridAttributes
And 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.