Can multiple NSArrayControllers control the same array? - cocoa

I want to keep several NSPopUpButtons synchronised with the same model object. I'm just getting familiar with bindings and have implemented the following scheme.
Is this a terrible idea?
Here each NSPopUpButton has their own NSArrayController. Each NSArrayController gets their content from the same NSMutableArray data source in my model layer.
I have noticed some strange problems when adding and removing objects to the model array (the array of animals in the example above) and was wondering how viable this approach is or whether there is a better way to keep several views in sync with the same model?

You should be fine doing this, as long as you keep your changes to the model in the main thread.
Each NSArrayController will listen for KVO notifications from your NSMutableArray and will update themselves accordingly.
If you change the array through one of the NSArrayController it will update your NSMutableArray, which again will trigger a KVO notification that will be caught by the other two NSArrayControllers.
It is important that you update your NSMutableArray in a KVO compliant manner. Namely you should be using the array proxy returned by mutableArrayValueForKey:

Related

group fetch / data updates (class structure design)

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

NSTableView & CoreData: Delete Object at clicked row

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.

How can I directly respond to NSTableView edits while still using NSArrayController?

In my Cocoa app, I have a sheet with a one-column NSTableView that lists a bunch of files in a directory (the app makes back-ups of it's main database, provides this list to users so they can revert to a particular back-up). The content is loaded into and provided to the table view by an NSArrayController, each object is just an NSFileWrapper (I'm considering using NSURL instead, but I digress). The NSArrayController handles sorting, enabling the buttons when a row is selected via bindings, that's all great. I have an NSWindowController subclass object (BackupsSheetController) that hooks all this up and exists in the sheet's nib.
However, when a user edits one of the cells, I want to respond to that change from BackupsSheetController by appropriately re-naming the file represented by that cell, putting it in its new location. Since the table view is bound to the NSArrayController, I don't get sent the NSTableViewDataSource message – tableView:setObjectValue:forTableColumn:row:. If I set my BackupsSheetController as the datasource for the NSTableView object in the nib, I get sent that message sometimes, but not very often, to say nothing of every time.
Most questions and examples I see out there for this scenario handle this all by using a custom model class for items in their table view, and make some controller object an observer for changing properties that they wish to respond to. In other words, each item would be something like a BackupNode object, and BackupsSheetController would observe each for changes to the name property (or whatever I would call it). That seems totally overkill for my scenario, but I also don't want to ditch the bindings I've already got in use and I don't see another way to do this. Is there another way to do this, to make sure I reliably get the setObject:... message? Or should I drop the NSArrayController and make BackupsSheetController the delegate and datasource for the table?
In the "BackupNode" scenario, I don't see why BackupsSheetController would observe each for changes in its name. That's a very roundabout way of doing things. I would think that the hypothetical BackupNode object would simply do the necessary work in its setter for the name property.
Anyway, I recommend using proper model objects. When you try to build a model with only Cocoa-provided objects like NSFileWrapper, NSURL, or NSMutableDictionary, you end up doing more work in the long run than if you just make a proper model object.
On a tangential topic, why is your window controller in the NIB? It should be the thing which loads (and owns) the NIB, which of course requires that it exist prior to the NIB being loaded, which means it can't be instantiated in the NIB.

Why would I use NSObjectController?

Although I have searched for many information about Cocoa Bindings, I still remain relatively unsatisfied with information I have and got. It seems that topic is somewhat troublesome for many and many are just avoiding this pattern, which I believe should not be.
Of course, it may seem that bindings are sometimes too complicated or perhaps designed with too much overhead...
However, I have one very direct and specific question: Why is NSObjectController needed if I can establish bindings directly?
For example, the code:
[controller bind:#"contentObject" toObject:self withKeyPath:#"numberOfPieSlices" options:nil];
[slicesTextField bind:#"value" toObject:controller withKeyPath:#"content" options:nil];
[stepperControl bind:#"value" toObject:controller withKeyPath:#"content" options:nil];
Does exactly the same as:
[slicesTextField bind:#"value" toObject:self withKeyPath:#"numberOfPieSlices" options:nil];
[stepperControl bind:#"value" toObject:self withKeyPath:#"numberOfPieSlices" options:nil];
In my case here, we are talking about property of the class inside which everything is happening, so I am guessing the need for NSObjectController is when:
key path for controller is object and binding of other controls is needed to its properties, not to its value as with primitives and wrappers around them is the case (numberOfPiesSlices in my case is NSInteger)
or when binding is needed from other outside objects, not only between objects within one
Can anybody confirm or reject this?
One of the benefits/points of bindings is to eliminate code. To that end, NSObjectController etc. have the benefit that they can be used directly in interface builder and set up with bindings to various UI elements.
Bindings only represent part of the functionality on offer. The *ObjectController classes can also automatically take care of a lot of the other more repetitive controller (as in Model, View, Controller) code that an application usually needs. For example they can:
connect to your core data store and perform the necessary fetches, inserts and deletes
manage the undo / redo stack
Pick up edited but not committed changes to your UI and save them (e.g. if a window is closed while focus is still on an edited text field - this was a new one to me, I found it from mmalc's answer in the thread below).
If you're doing none of this, then it probably isn't worth using NSObjectController. Its subclasses (NSArrayController etc) are more useful.
Also see here for a discussion of your exact question!
Why is NSObjectController needed if I can establish bindings directly?
I read this question a few days ago while looking for some information about NSObjectController, and today while continuing my search, I found the following passage which seemed relevant to the question:
There are benefits if the object being bound to implements
NSEditorRegistration. This is one reason why it’s a good idea to bind
to controller objects rather than binding directly to the model.
NSEditorRegistration lets the binding tell the controller that its
content is in the process of being edited. The controller keeps track
of which views are currently editing the controller’s content. If the
user closes the window, for example, every controller associated with
that window can tell all such views to immediately commit their
pending edits, and thus the user will not lose any data. Apple supply some generic controller objects (NSObjectController,
NSArrayController, NSTreeController) that can be used to wrap your
model objects, providing the editor registration functionality.
Using
a controller also has the advantage that the bindings system isn’t
directly observing your model object — so if you replace your model
object with a new one (such as in a detail view where the user has
changed the record that is being inspected), you can just replace the
model object inside the controller, KVO notices and the binding
updates.

Best way to handle multiple NSTableView(s)

What is considered the best way of handling multiple NSTableViews without using Cocoa Bindings?
In my app, I have two NSTableViews that are sufficiently closely related that I'm using the same object as the delegate and dataSource for both. The problem is both tableViews invoke the same methods. I currently discriminate between the two tableViews on the basis of NSControl -tag.
The deeper I get into this code, the uglier the use of -tag looks. I end up creating largely duplicate code to distinguish between the tableViews in each delegate/dataSource method. The code ends up being distinctly non-object oriented.
I could create a separate object to handle one or the other tableView, but the creation of said object would be a largely artificial construct just to provide a distinct delegate/dataSource.
Is everyone just using Cocoa Bindings now? I'm avoiding Bindings as I would like to hone my Cocoa skills on techniques that are transferrable between Mac OS and iPhone.
Every delegate/dataSource method for NSTableView passes the instance of NSTableView that's calling it as the first parameter (except for the ones that pass NSNotification objects, in which case the NSNotification's object is the table view instance). Some examples include:
- (int)numberOfRowsForTableView:(NSTableView*)aTableView;
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
If you're using one controller object as a delegate/data source for multiple tables, you can just use that parameter to differentiate between them.
for the method :
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
you can use :
NSTableView *theTable = (NSTableView *)[aNotification object];
if(theTable==listeDesMots)
...
It sounds like you should be using a different delegate object for each view, but the same data source. In other words a single model for distinct view and controller objects.
I don't think this is an artificial distinction because the objects have sufficiently different purposes, but you want to use the same data. The bigger rule you are violating now is that each object should have a single purpose. Each objects' purpose could be to retreive and display the data in a specific way.
Good Luck!

Resources