Relative newbie question. My app has a simple NSMutableArray of NSNumbers. (about a dozen integers) I'd like my UI to have a view displaying the numbers so that the user knows what's in the array. I want the contents of the view to be current, so I think I want a binding to the array (or its contents). Is there a simple way to do this?
I think I can figure this out if I change my model so that the NSMutableArray contains a custom class having a setter and getter to a declared property (following Lucas' YouTubtorial on NSTableView bindings), but I would think that there might be a simpler way, one that allows me to use my array of NSNumbers. I'd have to do a fair amount of editing if I have to redo my NSMutableArray. Thanks ...
You can use plain old NSNumbers (or anything else) in your model, no need to use a custom model class. However, you could create an NSValueTransformer subclass if your model data needs any special formatting for your view.
In your NIB you will have an NSTableView and an NSArrayController.
Bind the Value property of a TableView column to the NSArrayController, controller key = arrangedObjects, Model Key Path is empty (because you're viewing the NSNumber instance itself, and not a property of NSNumber).
Bind the Content Array property of NSArrayController to your model (the NSMutableArray of NSNumbers). This is probably a property on your view controller or app delegate.
That's about it. You can also hook up buttons to the add: and remove: actions on the NSArrayController, and you'll be able to add and remove items from your array.
Also, you need to send a KVO notification whenever your NSMutableArray changes. For example, say your NSMutableArray is exposed through a property called "numbers":
[self willChangeValueForKey:#"numbers"];
[_numbers addObject:[NSNumber numberWithInt:123]];
[self didChangeValueForKey:#"numbers"];
You get these notifications for free if you set the "numbers" property to a new value:
self.numbers = [NSMutableArray arrayWithObject:foo];
Related
What would be the best design for the following scenario?
I have a class that manages a bunch of NSManagedObjects. Inserting, deleting, fetching, etc. A viewController uses this object as the dataSource for a tableView. Thus every time the managed objects change (added, deleted or altered), the tableview has to reloadData().
To ensure that my class has the correct list of objects, it should fetch() the managedObjects after every delete or insert and notify any observers that its contents have changed.
So far this is all working nicely. However I would to limit the number of fetch() operations. Like NSView only draws once even though you called setNeedsDisplay multiple times. What is the best approach to do something similar to this?
It's kind of similar to a NSArrayController, but my class performs more functions in the backend while NSArrayController is more for binding views to the backend.
You should look for NSManagedObjectContextObjectsDidChangeNotification, posted by NSManagedObjectContext
The notification is posted during processPendingChanges, after the changes have been processed, but before it is safe to call save: again (if you try, you will generate an infinite loop).
The notification object is the managed object context. The userInfo dictionary contains the following keys: NSInsertedObjectsKey, NSUpdatedObjectsKey, and NSDeletedObjectsKey.
core data coalesce the changes for you, so it's already quite optimized.
Anyway, depending on what you want to do, a better option could be to subclass NSArrayController, probably overriding the - (NSArray *)arrangeObjects:(NSArray *)objects method, e.g:
- (NSArray *)arrangeObjects:(NSArray *)objects
{
NSArray *a1 = [self mayBeYouWantToPreprocessFetchedObjects:objects];
NSArray *a2 = [super arrangeObjects:a1]; // this performs filtering, etc
NSArray *a3 = [self mayBeYouWantToPostProcessArrangedObjects:a2];
// [self doWhatYouWantWithArrangedObjects:a3]; // e.g. trigger a reloadData if you're not using bindings
// or probably better : performOnMainThread: a method that will use arrangedObjects :
[self performSelectorOnMainThread:#selector(dataWasReloaded) withObject:nil waitUntilDone:NO];
return a3;
}
Doing so, you would get for free
all core data handling, including optimising the number of fetch (you can expect/hope NSArrayController is well optimised, and won't rearrange object when it's not necessary)
possibility to bind to model source like NSArray or NSSet in addition to core data (could be f.i. the arrangedObjects of another NSArrayController)
possibility to bind a NSTableView to your controller
all NSArrayController features, e.g. predicate filtering
I'm using such technique to provide data source to a NSOutlineView (partly because I have some specific processing on the fetched object, and also because NSTreeController is very limited), being still able to bind a NSTableView and have a flat view of my data
I am pretty new to Core Data and am currently working on a small (OSX) app that uses an NSTableView to organise objects. I would now like to delete a row/object with the click of a button on that targeted row.
I access the managed object within the table controller by calling [NSApp managedObjectContext] (still trying to figure out that dependency injection thing) but I can't easily delete an objectAtIndex: like I used to with the array (which has now been replaced by the core data stack, right?).
How do I identify the object to be deleted? And consequently, how can I cleanly remove it from the stack?
This is probably a really basic question but I couldn't find any resources on it. Bindings obviously don't work because the row does not get selected before the click occurs.
Any help is much appreciated!
Bindings would work, in that you could have the button's IBAction query the objectValue for the parent NSTableCellView. Once you have that objectValue, you could call the bound arrayController to delete the object, and then the cell/row would disappear.
So, if you have a tableCellView that has a delete button with an IBAction, within that IBAction, you could get the sender's superview, ensure it's an NSTableCellView, get the objectValue, and call [myArrayController removeObject:...]
As it says in the NSTableCellView class reference:
The objectValue is automatically set by the table when using bindings or is the object returned by the NSTableViewDataSource protocol method tableView:objectValueForTableColumn:row:.
This is actually a typical pattern with views in cocoa. objectValue or often representedObject are properties on the views that refer to the data model objects they represent, so if you have a view pointer from sender on the IBAction, you can get the related data model object. And if you're using bindings and a controller, you can then just have the controller remove that object.
With bindings, you will often create buttons that need IBActions attached, rather than some direct binding. But those IBActions can most definitely interact with the controller and not the view.
And with core data, array controllers are really slick vs. assuming you have to do it all programmatically.
I'm fairly new to obj-c and cocoa so please bear with me:
I have a NSTableView set up with cocoa bindings which works as expected with the simple -add -remove, etc methods provided by an instance of NSArrayController in my nib. I would like to programmatically add objects to the array that provides content for this controller (and hence for the table view) and then update the view accordingly.
I current have a working method for adding a new object to the array (verified by NSLog) but I can't figure out how to update the table view.
So: How do I update the bound tableview? (ie, after I have programmatically added objects to my array). I'm essentially after some view refreshing code like [view reloadData] in glue code, but I want it to work with the bindings I have in place.
Or is there a KVC/KVO related solution to this problem?
Code Details:
AppController.h
#interface AppController : NSObject
#property NSMutableArray *clientsArray;
-(IBAction)addClientFooFooey:(id)sender;
#end
AppController.m (note, I also have the appropriate init method not shown here)
#implementation AppController
...
-(IBAction)addClientFooFooey:(id)sender{
[self.clientsArray addObject:[[Client alloc] initWithFirstName: #"Foo" andLastName:#"Fooey"]];
//Need some code to update NSTableView here
}
#end
Client.h just simply defines two properties: firstName and lastName. The 2 columns of an NSTableView in my mainmenu.nib file are appropriately bound to these properties via an array controller bound to my AppController instance.
On a side note/as an alternative. How could I add functionality to the existing NSArrayController method -add, ie, something like: -addWithFirstName:andLastName and still have this compatible with bindings?
You have two main options for doing this provided your array controller is bound to clientsArray.
The first way is to just use the array controller's addObject: method instead of adding objects directly to clientsArray.
The other way is to keep your current addClientFooFooey: method but wrap your existing code with these two lines:
[self willChangeValueForKey:#"clientsArray"];
[self didChangeValueForKey#"clientsArray"];
This tells the KVO system that you are making a change to the array so it will go and look at it again.
The first option is the most straightforward, but if for some reason you need to update the array directly just let KVO know you are doing it.
I currently have a NSArrayController whose content property is (programmatically) set to an NSMutableDictionary and my UI has a single NSTableView. The information that the NSMutableDictionary contains is not set via the UI.
How would I use Cocoa bindings to display the dictionary keys in NSTableView? I already know how to use the DataSource methods, I just want to know how to use bindings for this.
I don't think it makes sense to set an array controller's content to an NSMutableDictionary. Rather, you should bind the array controller to the dictionary's allKeys property. Or consider using an NSDictionaryController, which was designed specifically for this purpose.
in my application I made a very simple binding. I have a NSMutableArray bound to a NSArrayController. The controller itself is bound to a ComboBox and it shows all the content of the NSMutableArray. Works fine.
The problem is : The content of the Array will change. If the user makes some adjustments to the app I delete all the items in the NSMuteableArray and fill it with new and different items.
But the binding of NSMutableArray <-> NSArrayController <-> NSComboBox does not refresh.
No matter if I remove all objects from the Array the ComboBox still shows the same items.
What is wrong here? Is my approach wrong or do I only need to tell the binding to refresh itself? I did not find out how to do that.
You're likely "editing the array behind the controller's back", which subverts the KVO mechanism.
You said:
I have a NSMutableArray bound to a NSArrayController.
How? Where does the array live? In a document, accessible via a KVC/KVO compliant -myArray / -setMyArray: set of accessors?
I'll bet you're directly telling the "myArray" ivar to -removeAllObjects, right? How will these KVC/KVO accessors "know" the array has changed?
The answer is, they don't. If you're really replacing the whole array, you'll want to tell your document (or whoever owns the array) to -setMyArray: to a whole new array. This will trigger the proper KVO calls.
... but then, you don't really need a mutable array, do you? If you only want to replace individual items in the array, you'll want to use indexed accessors:
(Documentation - see the Collection Accessor Patterns for To-Many Properties section)
http://tinyurl.com/yb2zkr5
Try this (using ARC/OS X 10.7):
in header file, define the arrayInstance and the arrayController
#property (weak) IBOutlet NSArrayController *arrayController;
#property (strong) NSArray *arrayInstance; // for the array instance
then in implementation
#synthesize arrayController = _arrayController;
#synthesize arrayInstance = _arrayInstance;
_arrayInstance = ....... // What ever the new array will be
[_arrayController setContent:_arrayInstance];
This will force the arrayController to update the content and display correctly.
Another but 2 line of code solution would be:
[self willChangeValueForKey:#"arrayInstance"];
_arrayInstance = ....... // What ever the new array will be
[self didChangeValueForKey:#"arrayInstance"];
Think the first looks more obvious, the second more KVO-like.
KVC/KVO compliance seems to be the problem. You should create the new array and update the reference with the new object by using the generated accessor methods. You may otherwise fire KVO messages about the array being updated to inform the bindings, that the contents of the array have changed.
Christian