How can I prevent the parent nodes of my NSOutlineView from getting sorted? - objective-c

I've would like to not sort the parent nodes of my NSOultineView.
The datasource of my outline view is a NSTreeController.
When clicking on a column header, I would like to sort the tree only from the second level of the hierarchy, and their children and leave the parent nodes in the same order.
UPDATE
This is how I bind columns to the values and assign the sort descriptor.
[newColumn bind:#"value" toObject:currentItemsArrayController withKeyPath:[NSString stringWithFormat:#"arrangedObjects.%#", metadata.columnBindingKeyPath] options:bindingOptions];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:metadata.columnSortKeyPath ascending:YES selector:metadata.columnSortSelector];
[newColumn setSortDescriptorPrototype:sortDescriptor];

You can use custom comparator.
To demonstrate the basic idea of this approach, let's assume you use NSTreeNode as your tree node class. All root nodes are stored in an NSArray named content, and your three root nodes are cat, dog and fish:
NSTreeNode *cat = [NSTreeNode treeNodeWithRepresentedObject:#"cat"];
// ...the same for dog and fish
self.content = #[cat, dog, fish];
Then, create your NSSortDescriptor prototype. Note that you should use self as the key instead of the string you are comparing (in this case representedObject) to get access to the raw node object. In the comparator, check if the object is contained in your root objects array. If YES, just return NSOrderedSame as it will keep the order unchanged from the initial order in your content array, otherwise use the compare: method to do a standard comparison.
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"self" ascending:YES comparator:^NSComparisonResult(NSTreeNode *obj1, NSTreeNode *obj2) {
if ([self.content containsObject:obj1] && [self.content containsObject:obj2]) {
return NSOrderedSame;
}
return [obj1.representedObject compare:obj2.representedObject];
}];
[[outlineView.tableColumns firstObject] setSortDescriptorPrototype:sortDescriptor];
Edit 2
If you have multiple columns that need to be sorted separately, you cannot use self as the key for every column, as the key should be unique among all columns' sortDescriptorPrototypes. In this case, you can create a custom object as the representedObject and wrap all your data including a pointer back to the tree node in the object.
Edit 1
The correctness of the above-mentioned approach requires NSOutlineView to sort its rows using a stable sort algorithm. That is, identical items will always retain the same relative order before and after sorting. However, I couldn't find any evidence in Apple documentation regarding the stability of the sorting algorithm used here, although from my experience the approach above will actually work.
If you are feeling unsafe, you can explicitly compare your root tree nodes based on whether current order is ascending or descending. To do that, you need an ivar to save the current order. Just implement the outlineView:sortDescriptorsDidChange: delegate method:
- (BOOL)outlineView:(NSOutlineView *)outlineView sortDescriptorsDidChange:(NSArray *)oldDescriptors {
ascending = ![oldDescriptors.firstObject ascending];
return YES;
}
And change return NSOrderedSame to:
if ([self.content containsObject:obj1] && [self.content containsObject:obj2]) {
NSUInteger index1 = [self.content indexOfObject:obj1];
NSUInteger index2 = [self.content indexOfObject:obj2];
if (ascending)
return (index1 < index2) ? NSOrderedAscending : NSOrderedDescending;
else
return (index1 < index2) ? NSOrderedDescending : NSOrderedAscending;
}
Edit 3
If you cannot implement outlineView:sortDescriptorsDidChange: for whatever reason, you can manually attach an observer to your outlineView's sortDescriptors array:
[outlineView addObserver:self forKeyPath:#"sortDescriptors" options:NSKeyValueObservingOptionNew context:nil];
In this way you can get notified when user clicked the header as well. After that don't forget to implement the following observing method, as a part of KVO process:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"sortDescriptors"]) {
// Again, as a simple demonstration, the following line of code only deals with the first sort descriptor. You should modify it to suit your need.
ascending = [[change[NSKeyValueChangeNewKey] firstObject] ascending];
}
}
To prevent memory leaking, you have to remove this observer before outlineView is deallocated(if not earlier). If you are not familiar with KVO, please make sure to check out Apple's guide.

Related

How to efficiently flatten a NSOutlineView?

I have a NSOutlineView which is binded to my NSTreeController. The content of the NSTreecontroller (myTreeController) is set with data using the command:
[self.myTreeController setContent:self.myArrayOfFiles];
The content of the array consists of NSTreeNode parent and children objects that are added using a NSTreeNode subclass (TKnode):
[TKnode treeNodeWithRepresentedObject:myRootObject].
This works very well and displays my NSoutlineView correctly. But when I want to iterate over the content of the NSOutlineView I need to flatten it and store the objects in an array. This is where I could need some help as the solution I have is not optimal and potentially prone to errors.
I first return the content of the NSTreeController using:
- (NSArray *)rootNodes;
{
return [[myTreeController arrangedObjects] childNodes] ;
}
This returns a NSTreeControllerTreeNode array of size one that holds the tree structure. I then access the first layer of the tree structure using the childNodes method.
- (NSArray *)flattenedNodes
{
NSMutableArray *mutableArray = [NSMutableArray array];
for (TKnode *rootnode in [self rootNodes]){
for (TKnode *node in [rootnode childNodes]){
if (![[node representedObject] isLeaf]){
[mutableArray addObjectsFromArray:[self descendants:[node representedObject]]];
}
else {
[mutableArray addObject:[[node representedObject] representedObject]];
}
}
}
DLog(#"My files: %lu",[mutableArray count]);
return [[mutableArray copy] autorelease];
}
The children of the children are accessed recursively using the following method:
- (NSArray *)descendants:(TKnode *) node
{
NSMutableArray *descendantsArray = [NSMutableArray array];
for (TKnode *mynode in [node childNodes]) {
[descendantsArray addObject:[mynode representedObject]];
if (!mynode.isLeaf){
[descendantsArray addObjectsFromArray:[self descendants:mynode]];
}
}
return [[descendantsArray copy] autorelease]; // return immutable
}
This works and returns an array containing all of my objects, not the NSTreeNodes, but the objects I am interested in. But it seems error prone that I have to call representedObject sometimes twice on an object to access my object. Is this the way I am supposed to work with NSTReeController and NSTreeNodes? Is there a better way? The reason I wanted to use NSTreeNode and NSTreeController in the first place was to get some of the already implemented methods for free such as sorting and arrangedObjects, which I am used to with NSTableView. But the method I use to access the NSTreeController content does not seem correct ? Should I use the arrangedObjects to get the content of the NSTReeController or should I use a different approach? Any suggestions for how to correctly flatten a NSOutlineView is highly appreciated.
Thanks! Cheers, Trond
This question turned out to be a real tumbleweed (my second in one month) so I wanted to follow up with some more info. I ended up using the above posted code for flattening my NSOutlineView as I was unable to find a better option. When you have to recursively iterate through an unknown number of subfolders (in my case) this seems to be the best option where the descendants method is called for each time you reach a deeper level. I found a very useful website with a number of useful NSTreeController extensions here, which uses the same approach as I had taken. Unless someone helps me figure out a faster and better algorithm for flattening the arrangedObjects of a NSTreeController, I believe i will stick with this approach.
Cheers, Trond
Update: After a question for the solution I decided to post my method for how to flatten an outlineview. I hope his can help others.
- (void) flattenOutlineview
{
NSMutableArray *nodeArray = [NSMutableArray array];
[self.myFlattenedFiles removeAllObjects];
[self.myFlattenedNodes removeAllObjects];
[self.myIndexDict removeAllObjects];
[nodeArray removeAllObjects];
for (SDNode *rootnode in self.rootNodes)
{
[nodeArray addObject:rootnode];
[self.myIndexDict setObject:[rootnode indexPath]
forKey:[[[rootnode representedObject] representedObject] fullpathL]];
for (SDNode *node in [rootnode childNodes])
{
if (node.isLeaf){
[nodeArray addObject:node];
[self.myIndexDict setObject:[node indexPath]
forKey:[[[node representedObject] representedObject] fullPathCopy]];
}
else {
[nodeArray addObjectsFromArray:[self descendants:node]];
[self descendantsIndex:node];
}
}
}
[self.myFlattenedNodes setArray:[nodeArray copy]];
for (SDNode *node in self.myFlattenedNodes)
{
[self.myFlattenedFiles addObject:[[node representedObject] representedObject]];
}
}
Saw this linked from another answer I just wrote. Provided you have everything in the tree expanded/displayed this would work:
for (NSUInteger row = 0; row < [outlineView numberOfRows]; row++)
{
NSTreeNode* node = [outlineView itemAtRow:row];
[nodeArray addObject:node];
}
The rows of the outlineView are basically a flattened tree. And you could perhaps expand every item while iterating to get them all.

NSMutableDictionary KVO

I'm trying to observe changes in dictionary using KVO.
Example:
dictionary = [NSMutableDictionary new];
[dictionary setObject:#"test1" forKey:#"key1"];
[dictionary setObject:#"test2" forKey:#"key2"];
[dictionary setObject:#"test3" forKey:#"key1"];
I'd love to be able to hook an observer for whenever a value is added to the dictionary. removed, or replaced (ie in the above cases, whenever any of the setObject methods are called)
So in conclusion:
I want a function to have
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
called when I ADD a any new entry to a dictionary, or Remove any entry, or REPLACE any entry.
NOT: I do NOT want to have to specify which keys I'm observing for. (eg observe only when #"key1" is added) as this solution doesn't scale.
Subclassing NSMutableDictionary is a bit annoying, due to the fact that NSDictionary and its friends are class clusters. It's certainly doable, and if you have to pass the object itself to another set of classes, then you may want to do exactly that. Otherwise, it might be easier to create a composite class which has the same basic API and uses NSMutableDictionary object internally for storage. There's a pretty good write-up as CocoaWithLove.com, Ordered Dictionary Subclassing, which goes into doing this.
However, that doesn't completely solve your problem. What I would suggest is that you begin with a subclass or decorator class such as the one above, then add support explicitly for -(NSArray*)allKeys, which is a standard accessor in NSDictionary itself. Then, you can add support to pass along change messages for allKeys, which will make it observable.
This can be done by adding the following code around the -setObject:forKey: and -removeObjectForKey: methods.
- (void)setObject:(id)anObject forKey:(id)aKey
{
BOOL addKey=NO;
if (![dictionary objectForKey: aKey]) {
addKey=YES;
[self willChangeValueForKey: #"allKeys"];
}
[dictionary setObject:anObject forKey:aKey];
if (addKey)
[self didChangeValueForKey: #"allKeys"];
}
- (void)removeObjectForKey:(id)aKey
{
[self willChangeValueForKey: #"allKeys"];
[dictionary removeObjectForKey:aKey];
[self didChangeValueForKey: #"allKeys"];
}
What is being done here is that we're adding explicit KVO notification to the class when the dictionary's keys are changed to mark a change in the array.
This will take care of adds and removes. If you want changes to be notified on the same basis, you can remove the if statements, and just have allKeys notify on either set or remove, like this:
- (void)setObject:(id)anObject forKey:(id)aKey
{
[self willChangeValueForKey: #"allKeys"];
[dictionary setObject:anObject forKey:aKey];
[self didChangeValueForKey: #"allKeys"];
}
Then, in your code, you put in a single observer for the key #"allKeys" on this object and you'll be receiving notifications whenever an item changes.
I solved a similar problem by adding an observer to the mutable dictionary "translator" in this way:
[self addObserver:self forKeyPath:#"translator.#count" options:0 context:NULL];
My app manages the data in a the classical way, using a tableview, a controller and the dictionary as KVO property "translator".
The dictionary is bound to a NSDictionaryController in my XIB, and a tableview content is bound to the controller.
This are the connections of the tableview:
Now, in any of the following cases I catch the change :
adding a key-value pair
removing a key-value pair
changing a key
changing a value
Remark: unfortunately, this approach does not work with NSMutableArrays.
Changes are not recognized
Can't you subclass NSMutableDictionary and override the various setters? For instance, overriding setObject:forKey: by calling super, then immediately calling addObserver...
You can also write a wrapper for NSMutableDictionary where you force yourself to use custom setters to manipulate the underlying NSMutableDictionary.
Maybe I need more context to any of your limitations or scalability intents.
I hope this will be helpful
- (void)addObserver:(id)observer {
for (id key in grid)
[self addObserver:observer
forKeyPath:[key description]
options:0
context:key];
}
I think another way to do this is using the below override, incase you are observing NSMutableDictionary "allRecentCurrencyData" whose values are dependent on recentBrazilReals, recentEuEuro, recentUkPounds, recentJapanYen, the observer will get called, but the drawback is you need to know the keys before hand to do this.
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:#"allRecentCurrencyData"]) {
NSArray *affectingKeys = #[#"recentBrazilReals", #"recentEuEuro",#"recentUkPounds",#"recentJapanYen"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}

UISearchBar Search table row with text, subtext and image

I've noticed that in order to do a search of a table, a copy of that data must be inserted to a search array.
E.g.
//Initialize the array.
listOfItems = [[NSMutableArray alloc] init];
NSArray *countriesToLiveInArray = [NSArray arrayWithObjects:#"Iceland", #"Greenland", #"Switzerland", #"Norway", #"New Zealand", #"Greece", #"Rome", #"Ireland", nil];
NSDictionary *countriesToLiveInDict = [NSDictionary dictionaryWithObject:countriesToLiveInArray forKey:#"Countries"];
NSArray *countriesLivedInArray = [NSArray arrayWithObjects:#"India", #"U.S.A", nil];
NSDictionary *countriesLivedInDict = [NSDictionary dictionaryWithObject:countriesLivedInArray forKey:#"Countries"];
[listOfItems addObject:countriesToLiveInDict];
[listOfItems addObject:countriesLivedInDict];
//Initialize the copy array.
copyListOfItems = [[NSMutableArray alloc] init];
So what is searched is the objects that are stored in the copied array.
My Question is, how do I search Cell rows with text, subtext and image in that particular cell.
(1)
There isn't really any such thing as searching a table. What happens when the user enters text in a UISearchBar is totally up to you - you can make that operation mean anything you like. All you have to do is function as the delegate-and-data-source for the results table and form the results table in response to the standard Three Big Questions that form the basis for any table ("how many sections have you? how many rows in this section? what's the cell for this row?") in any way you like. The results table does often look like a reduced version of the original table, but this is not at all required! It can be any table you want it to be.
(2)
Don't confuse Model with View. The table is just a view. Your data is Model. It is the Model, your data that is the basis of the original table, that you are going to be searching. So when the user types in your UISearchBar and you start searching, you want to form a new Model that will be the basis of the results table. How you form it is completely up to you. Typically you'll want to filter the original model so that the only stuff left in your results model is stuff that counts as a valid result. You could do this by walking the whole original model, putting everything that matches the search criterial into the new model. Or, if the original model is an array, you could use one of the filteredArray methods to help you. The most flexible way is to form a predicate with a block, as in this example from my book:
NSPredicate* p = [NSPredicate predicateWithBlock:
^BOOL(id obj, NSDictionary *d) {
NSString* s = obj;
NSStringCompareOptions options = NSCaseInsensitiveSearch;
return ([s rangeOfString:sbc.searchBar.text
options:options].location != NSNotFound);
}];
self.filteredStates = [states filteredArrayUsingPredicate:p];
In that example, s (one item of the array) is a string each time, and I'm looking to see whether the user's search term occurs in that string. But if you had a dictionary or other structure holding both a title and a subtitle and info about an image, you could examine that dictionary in any way you like. It's just a matter of returning YES or NO according to whether this array item passes the test based on the search term, on whatever definition you attach to the notion of passing the test.
(3)
The big question remaining is when to form the results model. I usually start by making the results model identical to the original model in response to searchDisplayControllerWillBeginSearch, because otherwise the results table will say No Results while the user is typing. (That is probably why you think the first thing to do is copy the original model.) Then, I can either do the actual filtering in response to searchBarSearchButtonClicked (the user is done typing and has tapped Search), or if the model is small enough, I can filter it afresh after every letter the user types, in response to searchBar:textDidChange (the user has typed a letter in the search bar).
There are a few steps involved. Note that the code below is just an example that I'm typing in by hand now, so it probably won't compile, it's just to give you an idea.
1) Ensure that you have an array containing all the cell values.
2) Create a copy of that array, and use that copy as the data source when returning cells in your table delegate methods.
3) Set yourself up as delegate for the UISearchBar, and respond to its events:
- (void)searchBarButtonClicked(UISearchBar *)searchBar {
[self doSearch:searchBar.text];
}
- (void)searchBar(UISearchBar *)searchBar textDidChange:(NSString *)searchTerm {
if (searchTerm.length == 0) {
[self resetSearch];
[table reloadData];
}
else
[self doSearch:searchTerm];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
searchBar.text = #"";
[self resetSearch];
[table reloadData];
[searchBar resignFirstResponder];
}
4) Create the other methods
The resetSearch method just needs to copy your full data array to the data source array used by your table delegates:
- (void)resetSearch {
self.tableSourceArray = [self.dataSourceArray copy]; // Or write a deep copy if you want to.
}
Whereas when searching, we need to filter the datasource array. You may be able to create something more efficient - this is just an example.
- (void)doSearch:(NSString *)searchTerm {
NSMutableArray *filtered = [[NSMutableArray alloc] init];
for (NSString *item in self.self.dataSourceArray) {
if ([item rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound])
[filtered addObject:[item copy]];
}
self.tableSourceArray = filtered;
}
And that should be it!
Tim

How do I persist data managed by NSArrayController without Core Data or NSKeyedArchiver?

I hope you'll excuse the seemingly broad nature of this question, but it gets quite specific.
I'm building a document-based Cocoa application that works like most others except that I am using SQLCipher for my data store (a variant of SQLite), because you don't get to set your own persistent data store in Core Data, and also I really need to use this one.
In my document sub-class, I've got an NSMutableArray property named categories. In the document nib I've got an NSArrayController bound to categories, and I've got an NSCollectionView bound to the array controller.
Each of my model objects in the array (each is a Category) is bound to a record in the underlying data store, so when some property of a Category changes, I want to call [category save], when a Category is added to the set, I want to call, again, [category save], and finally, when a category is removed, [category destroy].
I've wired up a partial solution, but it falls apart on the removal requirement, and everything about it seems to me as though I'm barking up the wrong tree. Anyway, here's what's going on:
Once the document and nib are all loaded up, I start observing the categories property, and assign it some data:
[self addObserver:self
forKeyPath:#"categories"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:MyCategoriesContext];
self.categories = [Category getCategories];
I've implemented the observation method in such a way as that I am informed of changes so that the document can respond and update the data store.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSNumber *changeKind = (NSNumber *)[change objectForKey:#"NSKeyValueChangeKind"];
if (context == MyCategoriesContext)
{
switch ([changeKind intValue])
{
case NSKeyValueChangeInsertion:
{
Category *c = (Category *)[change objectForKey:NSKeyValueChangeNewKey];
NSLog(#"saving new category: %#", c);
[c save];
break;
}
case NSKeyValueChangeRemoval:
{
Category *c = (Category *)[change objectForKey:NSKeyValueChangeOldKey];
NSLog(#"deleting removed category: %#", c);
[c destroy];
break;
}
case NSKeyValueChangeReplacement:
{
// not a scenario we're interested in right now...
NSLog(#"category replaced with: %#", (Category *)[change objectForKey:NSKeyValueChangeNewKey]);
break;
}
default: // gets hit when categories is set directly to a new array
{
NSLog(#"categories changed, observing each");
NSMutableArray *categories = (NSMutableArray *)[object valueForKey:keyPath];
NSIndexSet *allIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [categories count])];
[self observeCategoriesAtIndexes:allIndexes];
break;
}
}
}
else if (context == MyCategoryContext)
{
NSLog(#"saving category for change to %#", keyPath);
[(Category *)object save];
}
else
{
// pass it on to NSObject/super since we're not interested
NSLog(#"ignoring change to %#:#%#", object, keyPath);
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
As you can see from that listing (and as you might already be aware), it's not enough to observe the categories property, I need to observe each individual category so that the document is notified when it's attributes have been changed (like the name) so that I can save that change immediately:
- (void)observeCategoriesAtIndexes:(NSIndexSet *)indexes {
[categories addObserver:self
toObjectsAtIndexes:indexes
forKeyPath:#"dirty"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:MyCategoryContext];
}
This looks to me like a big kludge, and I suspect I'm working against Cocoa here, but for the most part it works.
Except for removal. When you add a button to your interface, and assign it to the array controller's remove: action, it will properly remove the category from the categories property on my document.
In doing so, the category is deallocated while it is still under observation:
2010-09-03 13:51:14.289 MyApp[7207:a0f] An instance 0x52db80 of class Category was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x52e100> (
<NSKeyValueObservance 0x2f1a480: Observer: 0x2f0fa00, Key path: dirty, Options: <New: YES, Old: YES, Prior: NO> Context: 0x1a67b4, Property: 0x2f1a3d0>
...
)
In addition, because the object has been deallocated before I've been notified, I don't have the opportunity to call [category destroy] from my observer.
How is one supposed to properly integrate with NSArrayController to persist changes to the data model pre-Core Data? How would one work-around the remove problem here (or is this the wrong approach entirely?)
Thanks in advance for any advice!
It would seem, based on some initial hacking, that subclassing NSArrayController is the way to go here. Over-riding the various insertObject(s) and removeObject(s) methods in that API gives me the perfect place to add in this logic for messing with the data model.
And from there I can also begin to observe the individual items in the content array for changes, etc, stop observation before destroying/deallocating them, etc, and let the parent class handle the rest.
Thanks for this solution is due to Bill Garrison who suggested it on the cocoa-unbound list.
I would observe changes to categories list, and when the list changes, store the array of categories away in a secondary NSArray, 'known categories', using mutableCopy. Next time the list changes, compare that 'known' list to the new list; you can tell which categories are missing, which are new, etc. For each removed category, stop observing it and release it.
Then take a new mutable copy for the 'known' list of categories, ready for the next call.
Since you have an additional array holding the categories, they aren't released before you're ready.

Using Array Controllers to restrict the view in one popup depending on the selection in another. Not core data based

I am working on an app that is not core data based - the data feed is a series of web services.
Two arrays are created from the data feed. The first holds season data, each array object being an NSDictionary. Two of the NSDictionary entries hold the data to be displayed in the popup ('seasonName') and an id ('seasonID') that acts as a pointer (in an external table) by matches defined for that season.
The second array is also a collection of NSDictionaries. Two of the entries hold the data to be displayed in the popup ('matchDescription') and the id ('matchSeasonId') that points to the seasonId defined in the NSDictionaries in first array.
I have two NSPopUps. I want the first to display the season names and the second to display the matches defined for that season, depending on the selection in the first.
I'm new at bindings, so excuse me if I've missed something obvious.
I've tried using ArrayControllers as follows:
SeasonsArrayController:
content bound to appDelegate seasonsPopUpArrayData.
seasonsPopup:
content bound to SeasonsArrayController.arrangedObjects; content value bound to SeasonsArrayController.arrangedObjects.seasonName
I see the season names fine.
I can obviously follow a similar route to see the matches, but I then see them all, instead of restricting the list to the matches for the season highlighted.
All the tutorials I can find seem to revolve around core data and utilise the relationships defined therein. I don't have that luxury here.
Any help very gratefully received.
This is not an answer - more an extension of the previous problem.
I created MatchesArrayController and subclassed it from NSArrayController to allow some customisation.
Following the example in 'Filtering Using a Custom Array Controller' from 'Cocoa Bindings Topics', I followed the same idea as above:
MatchessArrayController: content bound to appDelegate matchesPopUpArrayData.
matchesPopup: content bound to MatchesArrayController.arrangedObjects; content value bound to MatchesArrayController.arrangedObjects.matchDescription.
I've derived the selected item from seasonPopUp:sender and used this to identify the seasonId.
The idea is to change the arrangedObjects in MatchesArrayController by defining the following in;
- (NSArray *)arrangeObjects:(NSArray *)objects
{
if (searchString == nil) {
return [super arrangeObjects:objects];
}
NSMutableArray *filteredObjects = [NSMutableArray arrayWithCapacity:[objects count]];
NSEnumerator *objectsEnumerator = [objects objectEnumerator];
id item;
while (item = [objectsEnumerator nextObject]) {
if ([[[item valueForKeyPath:#"matchSeasonId"] stringValue] rangeOfString:searchString options:NSAnchoredSearch].location != NSNotFound) {
[filteredObjects addObject:item];
}
}
return [super arrangeObjects:filteredObjects];
}
- (void)searchWithString:(NSString *)theSearchString {
[self setSearchString:theSearchString];
[self rearrangeObjects];
}
- (void)setSearchString:(NSString *)aString
{
[aString retain];
[searchString release];
searchString=aString;
}
I've used NSLog to check that things are happening the way they are supposed to and all seems ok.
However, it still doesn't do what I want.
[self rearrangeObjects]; is supposed to invoke the arrangeObjects method but doesn't. I have to call it explicity
(i.e.[matchesArrayController arrangeObjects:matchesPopUpArrayData]; )
Even then, although filteredObjects gets changed the way it is supposed to, the drop down list does not get updated the way I want it to.