I have a NSOutlineView that show the content controlled by a NSTreeController which I bind to an NSMutableArray (arrayOfFiles). The array contains NSTreeNode objects where the representedObject (Fileobject class) holds a number of ivars. I would like to edit and update the ivar named "direction" for specific objects. I manage to get my object of interest using a NSIndexPath which I have stored for each object.
[self.myOutlineViewController.myTreeController setSelectionIndexPath:myIndexPath];
Fileobject *myObject =[[[self.myOutlineViewController.myTreeController.arrangedObjects descendantNodeAtIndexPath:myIndexPath] representedObject] representedObject];
[myObject setDirection:0];
This works fine, but I run into problems when I want to update the object I just extracted at NSIndexPath. The following crashes:[self.myOutlineViewController.myTreeController removeObjectAtArrangedObjectIndexPath:myIndexPath]; with the error message
An uncaught exception was raised
2012-11-08 17:23:25.557 S3access[20379:12b03] *** -[NSKeyValueSlowMutableArray removeObjectsAtIndexes:]: value for key myArrayOfFiles of object 0x40012e460 is nil
I understand i am doing something wrong with Key-Value coding here but I am unable to see what it is. I have tried to seek solutions in all of the examples from Apple but I can only find examples that do not use NSTreeController and NSTreeNode. My thought was to 1) extract the object at indexpath 2) edit the extracted object 3) remove current object at indexpath 4) insert my new edited object into indexpath using command [self.myOutlineViewController.myTreeController insertObject:myNode atArrangedObjectIndexPath:myIndexPath];. I dont see how I can replace my object using Key-Value coding when I dont replace only an ivar but the whole object?
Any suggestions for what I am doing wrong and suggestions for how I may solve this is highly appreciated.
Cheers, and thanks! Trond
I finally managed to get my update of NSOutlineView to work properly, and since my question was an extreme tumbleweed I thought I would at least update with an answer. It turns out my problem was more a lack of proper understanding of KVC rather than a programming problem. After having read answers to questions on Stackoverflow and the Apple documentation I finally figured out how to enable a dynamic update of my NSOutlineView and its NSTreeController contents (arrangedObjects) which are represented using NSTreeNode. The following code worked for me assuming you know the NSIndexPath (myIndexPath) of your object:
[self.myOutlineViewController.myOutlineView willChangeValueForKey:#"direction"];
[[[[self.myOutlineViewController.myTreeController.arrangedObjects descendantNodeAtIndexPath:myIndexPath] representedObject] representedObject] setDirection:0];
[self.myOutlineViewController.myOutlineView didChangeValueForKey:#"direction"];
Hopefully this may help some others. Cheers, Trond
Related
I have a problem.
I have an NSObjectController called "mapController" and I want to put some defaults when the object is created. I do this inside the windowControllerDidLoadNib method of my document, as suggested by the docs. But…
if (![mapController content]){ // No map defined yet.
[mapController add: self]; // This should create the instance.
NSLog(#"%#",[mapController content]); // Gives NULL.
I tried:
BOOL ok = [mapController fetchWithRequest:nil merge:NO error:nil];
NSLog(#"%#",[mapController content]); // Gives NULL.
The content of mapController is in the Core Data "scratch pad" but I can't access it. I have to set one of its attributes like this:
[[mapController content] setValue:[matrix colorReference] forKey:#"mapData"];
This gives no error, the file is marked as changed, but it I test the value:
NSLog(#"%#",[mapController content]); // Gives NULL.
When the heck it the controller's content really HERE? Something appears on the screen but… what actually? Reading the docs doesn't help me…
OK, I found the answer in the docs:
add: Creates a new object and sets it as the receiver’s content object.
Discussion
Creates a new object of the appropriate entity (specified by
entityName) or class (specified by objectClass)—see newObject—and sets
it as the receiver’s content object using addObject:.
Special Considerations
Beginning with Mac OS X v10.4 the result of this method is deferred
until the next iteration of the runloop so that the error presentation
mechanism can provide feedback as a sheet.
That's why
[[mapController content] setValue:[matrix colorReference] forKey:#"mapData"];
worked fine when called elsewhere in the app. It was a few iterations later…
Well… maybe this post will save you a couple of hours you could use to sleep longer.
Regards,
Bernard
I don't think its your mapController, I think it is your NSLog. Try this:
NSLog(#"%#", mapController);
also try getting simple data from the content, like the float value of the CGColorRef so you can use other formatters like %f.
I would have tested this but I cannot seem to create an instance of NSObjectController because it is an undeclared identifier. What framework is it defined in? Did you have to #import anything?
I've got a UIViewController which has three views: Category -> Sub-Category -> Detail View. The content of the Sub-Category and Detail View depends on which row is clicked in the Category view. This is done with a property called 'categoryClicked' and is declared in the sub-category.m file. It's value is given in the category.m file by the following lines of code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
Sub-category *sub = [[Sub-category alloc] initWithNibName:#"Sub-category" bundle:nil];
sub.categoryClicked = [_categoryArray objectAtIndex:indexPath.row];
[self.navigationController pushViewController:rubrieken animated:YES];
}
This works perfectly fine until I want to use the value of categoryClicked in the Detail View. In both the category.m and the DetailView.m sub-category.m is imported. For testing purposes I putted a label on the Detail View and in the DetailView.m file I've got the following code:
Sub-category *sub = [[Sub-category alloc] initWithNibName:#"Sub-category" bundle:nil];
label.text = sub.categoryClicked;
I'm convinced this code should do the job but in fact I get an empty label. Is there anybody able to tell me what I'm doing wrong.
Edit
categoryClicked is a property declared in Sub-category.h and synthesized in Sub-category.m.
Wanted to post some more code but there is no more relevant code.
This line...
Sub-category *sub = [[Sub-category alloc] initWithNibName:#"Sub-category" bundle:nil];
...creates a new Sub-category. Since it's new, it doesn't know anything about what information has been given to some other Sub-category object. You need to get a reference to the existing object if you want to access its data.
When you alloc an object, you're allocating memory space for it. Then when you use something with init (like initWithNibName) you're initializing it. So when you allocate memory space for sub and initialize it, you have one object. But then you alloc and init again, which creates an entirely new (and completely unrelated) object. Make sure that you remove anything that could destroy your old object.
Also, the * symbol means that sub is a pointer (it points to a memory location). Whenever you use the assignment operator (=) you're telling it to point to a new thing. What you're doing is telling the label.text pointer to point at what sub is pointing at. But if you change what sub is pointing at and point label.text at the same thing, neither one is pointing at the value you want.
Hope this is clear enough, if it's not trying posting some more code and maybe someone can suggest exact changes.
-EDIT-
If you want to have a reference to an object you can only get it a few ways. What's important to know is that you can't really "create" a reference to an existing object. You have to have some connection to the object.
Declare the object inside of the file you want the reference in with something like Category c = [[Category alloc] init]; Remember this creates a new object, it won't create a reference to an existing object. However, creating the object inside of another object means that one "owns" the other and can do whatever it wants with it (obviously including accessing properties and calling methods).
Use a "chain" of objects to get a reference to your object. So if your file owns a file that owns the object you want you can use topfile.otherfile.objectyouwant. The most obvious example of this is getting a reference to an object owned by a subview.
That's about as basic as it gets; just remember that there aren't any "global" objects that you can just call by name. If your problem isn't solved by this, either look at some sample code and try to figure out how references work, or post another question that's more closely related to your problem
I have an NSMutableDictionary with keys and values set up initially.
Later I need to update the values for certain keys, and I thought I would just need to do
[mutableDict setValue:val forKey:key], but this throws an exception saying:
[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object
What am I doing wrong?
(I have another mutable dictionary inside each key of the mutable dictionary, but I don't think that's why)
I'll copy an paste my comment just incase you want to keep your 100% accept
Paul.s
For some reason you are actually working with an NSDictionary not an NSMutableDictionary. Hard to say why without more code, maybe you are using a copy obtained by calling copy or you have loaded the dictionary from a plist/userDefaults.. Can we see some more code?
Dennis
#Paul.s Yes, I did copy an NSMutableDictionary. Would that be why? If it is, then what else should I use to copy?
Paul.s
Use mutableCopy instead of copy to get a mutable dictionary
Dennis
#Paul.s oh.. didn't know about that. I'm new to Objective-C and I tried to search for what I am doing wrong but I missed it :( Thank you!!
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.
I created a separate NSManagedObjectContext on a separate thread to perform some store maintenance. However, I have noticed that the context returns YES for hasChanges as soon as a managed object in it is even referenced e.g.
NSString *name = managedObject.name;
This context is created and used exclusively in 1 method. Why is it returning has changes, when there there are none?
That ks difficult to answer without seeing the code. Perhaps your object has a -awakeFromFetch call that touches a property or something else. Normally there should be no changes from just fetching an object unless you are doing something to that object either in the awakeFromFetch or somewhere else in your code.
update
Before the save, grab the deleted array, updated array and inserted array and take a peek at them. That will give you a hint as to what is going on.