Best way to add/remove to an NSMutableArray managed by an NSArrayController - objective-c

I've got a NSMutableArray (containing NSMutableDictionary instances) bound to an NSArrayController (the NSArrayController is in turn bound to NSTableView columns).
What is the most Cocoa-, and KVO- friendly way of, programmatically :
adding a new empty object (NSMutableDictionary) to the array?
removing currently selected object? (after removing, the previous item - if exists - should be selected)
I've always been doing this in a way I don't particularly like - and I'm sure it's not the best way around (too many lines of code for something so simple : in Cocoa that indicates a wrong take on the subject :-)).
My code (quite an overkill, actually) :
Adding to the Array
NSMutableArray* oldParams = [paramsArray mutableCopy];
[oldParams addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:#"Parameter",#"Parameter",#"",#"Value", nil]];
[self setParamsArray:oldParams];
[paramsController setSelectionIndex:[paramsArray count]-1];
Removing currently selected object from the Array
if ([paramsArray count]>0)
{
int s = [paramsController selectionIndex];
NSMutableArray* oldParams = [paramsArray mutableCopy];
[oldParams removeObjectAtIndex:s];
[self setParamsArray:oldParams];
if (s<=[paramsArray count]-1)
[paramsController setSelectionIndex:s];
else
[paramsController setSelectionIndex:[paramsArray count]-1];
}
So, what are your opinions on that?

Given that the array controller is bound to a property named paramsArray on some object, the best approach is to define the key-value coding indexed accessors on that object's class. Then, use those accessors to mutate the to-many relationship represented by the property in a KVO-compliant manner.
For example, implement -insertObject:inParamsArrayAtIndex: and then use that to add an object. If you like the convenience of NSMutableArray's -addObject: method, you can write an -addObjectToParamArray method that forwards to -insertObject:inParamsArrayAtIndex:.
By the way, "paramsArray" is a poor name for a property. The property name shouldn't encode the type used to implement it. If you look at the templates for the indexed accessor names, you'll see that Apple is expecting to-many relationship properties to just be a plural noun like "params" (no "Array"). For example, -paramsAtIndexes: is better than -paramsArrayAtIndexes:.

You have to think of your array as the controller's backing store, and that it's managing it for you.
Adding an object:
[[self accountsArrayController] addObject:accountDictionary];
Removing the currently selected object:
[[self accountsArrayController] remove:nil];
You'll have to write another line or two to make that previous item selected, but that's an exercise I leave to you.

Related

Is it possible to know an array (or arrays) which adding an object?

Follow is some code for example.
NSArray *test1 = [[NSArray alloc] initWithObjects:#"TEST", nil];
[someArray addObject:test1];
:
:
too many code lines.
:
:
At some place
NSArray *addingArray = [test1 whoisAddingOrContainingMe(?)];
I want to know a pointer of someArray as method of test1 instance.
Is there a method like this?
No, you can't "reverse lookup" the containers you are contained in.
From a design perspective this would be somewhat difficult, since conceptually there's no difference between having a reference to oneself in an "array", in any other container, or in any other object that's not considered to be a container. Thus, you have to record every single "retain" by passing it an additional "owner" parameter, and since retains and releases can be done in vastly different places you would also need to pass "owner" pointers around so that an eventual "release" can refer to the proper retain.
Or, to put it short: it would be a huge mess :-)
As suggested before, if you know what arrays can actually contain you -- and that should be much easier for your application -- you could check them. Or you could add a list to the objects to record where they have been added, probably via methods like "addTo:" and "removeFrom:".
I think you want NSArray's -containsObject: method.

Picking an item of NSArray using key value coding

If you read listing 4 in the Animation section Apple's Core Animation guide, it seems to use KVC and the key path "filters.pulseFilter.inputIntensity" to pick out an object called "pulseFilter" out of an NSArray. "pulseFilter" is actually a CIFilter named "pulseFilter" by calling the method setName.
Now, I don't see the method setName defined anywhere. I also don't believe you can select a specific item out of an NSArray by using a key. Can someone explain how this works?
It does not necessarily have to go through standard valueForKey: or valueForKeyPath: of the NSArray.
Objects are free to provide own valueForKeyPath: method and handle KVC its own way and this is what probably the class of this selectionLayer object does.
To retrieve an object from NSArray using a property name + property value.
Step 1: get your stored object compliant to KVC (NSKeyValueCoding protocol) by implementing valueForKey: in your custom class.
Step 2: filter your NSArray using NSPredicate (filteredArrayUsingPredicate on your NSArray)
Do you need more detail ?

Sorting a column in NSOutlineView that is bound to NSTreeController

I have an NSOutlineView that is bound to an NSTreeController.
In Interface builder, I have bound each column of NSOutlineView to the tree controller with the Controller key "arrangedObjects" and model key path as the entity attribute or a method in my entity class.
Now all other columns sort perfectly except for one special column. The special column has a model key path bound to a method that is declared in my Entity class. This method depending on some condition in my code will return either NSString or NSDictionary.
When it returns an NSDictionary, the delegate method:
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
parses the NSDictionary and returns a double value for that cell. Also note that the cell in this case is derived from NSCell and displays a progress bar.
In short, my special column will display a mix of progress bars and strings depending on the situation.
I would like to implement sorting so that all progress bars stay together and the strings are sorted alphabetically.
Perhaps you will have some luck if you try setting the table column's sort descriptor to a descriptor you create with a comparator that looks at the objects' classes. (You could also try filling in the "Sort Key" in IB, using the "class" key, but I think this might be less likely to work...)
Well i found a solution to this myself and just 2 minutes after posting my query :) The solution was to add a custom sort key in IB and define a method with that key name in my Entity class that returns an NSInteger. Since i want all NSDictionary objects to stay together and all NSStrings to be sorted, i return appropriate integer based on the object type.
Note for anyone who might be stuggling with sort not working: Make sure binding for sortDescriptor is enabled in IB.
try
NSSortDescriptor *sorter = [[[NSSortDescriptor alloc]
initWithKey:NULL
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)] autorelease];
[[myOutlineView tableColumnWithIdentifier:#"Name"]setSortDescriptorPrototype:sorter];

What is the difference between valueforKey:, objectForKey:, and valueForKeyPath:?

I have 2 questions:
What is the difference between valueForKey: and objectForKey:? Is it that one is for NSDictionarys (objectForKey:) and for others it is valueforKey:, or is it the reverse?
Also what is the difference between valueForKey: and valueForKeyPath:? Has it got something to do with Core Data?
Please help.
valueForKey: is part of the NSKeyValueCoding protocol and is therefore part of the key-value coding framework, which allows you to access class properties by name at runtime. That's how NIBs are loaded, for example — the names of properties associated with connections are loaded and then the values are set directly by name. This contrasts with the way that visual interface design tools often work in other languages, generating lots of hidden statically compiled code.
objectForKey: is defined on dictionaries only, and looks up an object by its key. An NSDictionary is a class that stores connections between values and keys.
So, valueForKey: could be used on an NSDictionary to return meta information about the dictionary, such as the count of objects inside it, the list of all keys, etc. objectForKey: would be used actually to look into the dictionary.
At runtime, the difference is that objectForKey: is a method with a completely opaque implementation. valueForKey: explicitly relies on subsequently calling named getters and setters. The reason for the latter is that you can extend key-value coding to key-value observing, where you ask to be informed every time a particular property on a particular object changes. At runtime that's achieved with a method swizzle, where the original setter is replaced by a new one that calls the previous setter and then sends out the required messages. Because all messages are dispatched dynamically, that's just achieved by modifying tables within the object instance.
So any object that is key-value coding compliant (which just means declaring and implementing your properties in the proper way, which the new-ish #property/#synthesize syntax does automatically) can be observed without the object itself having to implement any code.
There's further Apple stuff that uses key-value coding to achieve various things, including CoreData, but it's not specifically to enable any one other technology.
valueForKeyPath: is like valueForKey: except that it can traverse several objects. So you can have a root object with a bunch of properties, each of those properties is another object with another bunch of properties, etc, and using a key path you can retrieve a value way out at the leaf of that data structure rather than having to iterate through object after object for yourself.
In summary, valueForKey: and valueForKeyPath: provide information about object instances and interact with the dynamic nature of the Objective-C runtime. objectForKey: is a dictionary specific method that does dictionary tasks.
Additions:
An example, coded as I type and assuming that NSDictionary is key-value coding compliant:
NSDictionary *someDictionary;
// create someDictionary, populate it, for example (note: we assume photoOfKeys.jpg
// definitely exists, not a good idea for production code — if it doesn't we'll get
// a nil there and anything after it won't be added to the dictionary as it'll appear
// that we terminated the list):
someDictionary = #{ #"favouriteGarment": #"hat",
#"#allKeys" : [NSImage imageNamed:NSImageNameDotMac],
#(2) : NSArray.new };
NSObject *allKeys;
// we make no assumptions about which type #allKeys will be, but are going to assume
// we can NSLog it, so it needs to be a descendant of NSObject rather than 'id' so as
// to definitely respond to the 'description' message — actually this is just compile
// time semantics, but if someone else reads this code it'll make it obvious to them
// what we were thinking...
// some code to get all of the keys stored in the dictionary and print them out;
// should print an array containing the strings 'favouriteGarment', '#allKeys' and
// the number 2
allKeys = [someDictionary valueForKey:#"#allKeys"];
NSLog(#"%#", allKeys);
// some code to get the object named '#allKeys' from the dictionary; will print
// a description of the image created by loading photoOfKeys.jpg, above
allKeys = [someDictionary objectForKey:#"#allKeys"];
NSLog(#"%#", allKeys);
// `objectForKey is analogous to `objectForKeyedSubscript:`, aka
allKeys = someDictionary[#"#allKeys"];
allKeys is a property of NSDictionary as described here. I've also added a mapping from the NSString allKeys to a photograph of some keys. Whether I use the key-value coding valueForKey: methods or the NSDictionary objectForKey: lookup method dictates whether I read the property of the object instance or whether I send the object instance a message asking it to do its unique job.
objectForKey: is a method on NSDictionary for accessing the object associated with a key. valueForKey: is a method on NSObject for accessing any value associated with any object, through the name of a accessor method, property, and/or instance variable.
valueForKeyPath: can be seen as a shorthand for several calls to valueForKey:. You can think of it as sort of a xpath, if you will.
These two statements will result in the same output:
// Using nested valueForKey:
NSLog(#"%#", [[myObject valueForKey:#"foo"] valueForKey:#"bar"]);
// Can be done with a single valueForKeyPath;
NSLog(#"%#", [myObject valueForKeyPath:#"foo.bar"]);
valueForKey:and valueForKeyPath: are part of KVC (Key Value Coding). Introduction and in-depth documentation can be found here: http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/
valueForKey: and valueAtKeyPath: are methods defined in the NSKeyValueCoding informal protocol, and default implementations for both are provided by the root class NSObject.
objectForKey: is a method on NSDictionary.
valueForKey: takes a key to a property, while valueAtKeyPath: takes a so-called keypath. A keypath is a period-delimeted path to a specific property, like #"relationship.property".

Populating NSTableview from a mutable array

I've been attempting this for two days, and constantly running into dead ends.
I've been through Aaron Hillegass's Cocoa Programming for MAC OS X, and done all the relevant exercises dealing with NSTableview and mutable arrays, and I have been attempting to modify them to suit my needs.
However none of them seem to be using an array with objects as a data source, it seems to use the tableview as the datasource.
I'm trying to implement Jonas Jongejan's "reworking" of my code here, with a Cocoa front end to display the results.
Any pointers or suggestions I know this should be simple, but I'm lost in the wilderness here.
I can populate the table by setting the array
It's pretty simple really, once you get to understand it (of course!). You can't use an NSArray directly as a table source. You need to either create a custom object that implements NSTableViewDataSource or implement that protocol in some existing class - usually a controller. If you use Xcode to create a standard document based application, the document controller class - (it will be called MyDocument) is a good class to use.
You need to implement at least these two methods:
– numberOfRowsInTableView:
– tableView:objectValueForTableColumn:row:
If you have a mutable array whose values you'd like to use in a table view with one column, something like the following should do as a start:
– numberOfRowsInTableView: (NSTableView*) aTableView
{
return [myMutableArray count];
}
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
return [myMutableArray objectAtIndex: rowIndex];
}
It has just occurred to me that you could add the above two methods as a category to NSArray replacing myMutableArray with self and then you can use an array as a data source.
Anyway, with a mutable array, it is important that any time you change it, you need to let the table view know it has been changed, so you need to send the table view -reloadData.
If your table view has more than one column and you want to populate it with properties of objects in your array, there's a trick you can do to make it easier for yourself. Let's say the objects in your array are instances of a class called Person with two methods defined:
-(NSString*) givenName;
-(NSString*) familyName;
and you want your table view to have a column for each of those, you can set the identifier property of each column to the name of the property in Person that that column displays and use something like the following:
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
Person* item = [myMutableArray objectAtIndex: rowIndex];
return [item valueForKey: [tableColumn identifier]];
}
If you replace valueForKey: with valueForKeyPath: and your Person class also has the following methods:
-(Person*) mother;
-(Person*) father;
-(NSString*) fullName; // concatenation of given name and family name
you can add table columns with identifiers like: father.fullName or mother.familyName and the values will be automatically populated.
You could go the datasource route and do all of the heavy lifting yourself, or you could let bindings do all the heavy lifting for you. Add an NSArrayController to the nib file that has the table view in it. Make sure that the File's Owner of the nib is set to the same class that has the mutable array in it. Bind the contentArray of the array controller to File's Owner.myMutableArray. For each column bind Value to the array controller arrangedObjects and add the appropriate key path. This will allow you to get things like user sorting for free if you ever need it.
On the iPhone (I know you're talking about Mac, but maybe this could help) you have to use delegation for loading a tableView. It asks for a cell and you use your array to fill-in the data where needed.
I'm not sure if this works for the Mac, but it'd be worth looking into.
Maybe set dataSource to self and use those delegate methods to access your array based on the row and column #
Apple has a whole guide for Table View Programming so I suggest you start with the Using a Table Data Source section of the that guide.