Refresh Cocoa-Binding - NSArrayController - ComboBox - cocoa

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

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

Programmatically update a TableView that is governed by Cocoa Bindings

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.

Xcode, OS X: viewing a simple NSMutableArray

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];

Manual binding in Cocoa

I have an ImageView which shows a lock, informing if an opened file is locked or not. I have 2 images for locked and unlocked cases. I want synchronize the displayed image with boolean value of my object representing an opened file.
To do this I want my ViewController to change the image in my ImageView depending on lock state of object. So both object and ViewController have a property "isLocked".
How can I synchronize them? It is easy in IB but I don't know how to do it programmatically. I tried in initialize method of my ViewController to use:
[ViewController bind:#"value" toObject:[ArrayController selection] withKeyPath:#"isLocked" options:nil];
But it doesn't work. In documentation it is said that I have to expose my binding before using it.
I try to put the following code in initializer method of my object:
[self exposeBinding:#"isLocked"];
But Xcode doesn't recognize this method.
Does somebody have experience with this kind of bindings establishing?
As #nick says, you want Key-Value-Observing.
[arrayController addObserver:self
forKeyPath:#"selection.isLocked"
options:NSKeyValueObservingOptionNew
context:#"this_context"]
Then when isLocked changes the -observeValueForKeyPath:ofObject:change:context: method that you have added to your viewController will be called (as long as you only manipulate isLocked in a KVC compliant way).
The options parameter lets you optionally tweak exactly what conditions will trigger the notification and what data is sent along with the notification. The context parameter is there to help you distinguish between notifications that you registered to receive and notifications your superclass registered to receive. It is optional.
Bindings seem like they might be useful to keep two values in sync. However, this is not what they do at all.
Yes, lots of things seem to give the impression that this is what they do, and there isn't much saying that this isn't what they do, also lots of people believe that this is what they do - but no, you cannot use them for this.
Only a handful of classes support bindings (they are listed here) and then, and this is the important bit, those classes only support binding their named bindings, and these bindings are not instance variables. eg NSTextField has a 'fontFamilyName' binding yet NSTextField does not have a 'fontFamilyName' property or instance variable, even a derived one. NSTextField does have a 'isBordered' property but not a binding - so you cannot bind 'isBordered'.
It does not mean anything to 'bind' an arbitrary property of an arbitrary Class.
Yes, you can bind two arbitrary values, the following code works just fine:
#import <Cocoa/Cocoa.h>
#interface SomeObject : NSObject
#property (retain,nonatomic) id someValue;
#end
#implementation SomeObject
#end
int main()
{
SomeObject *source=[SomeObject new];
SomeObject *target=[SomeObject new];
[target bind:#"someValue" toObject:source withKeyPath:#"someValue" options:0];
[source bind:#"someValue" toObject:target withKeyPath:#"someValue" options:0];
[source setSomeValue:#(42)];
NSLog(#"target: %#",[target someValue]);
[target setSomeValue:#(22)];
NSLog(#"source: %#",[source someValue]);
return 0;
}
As far as I can tell, the problem is the bit [ArrayController selection]. The first problem is that ArrayController is (or should be) a class, and getting the class's selection is probably pointless. The other problem is that even if this were an instance, you would be binding to the selection at the time of the call, which is almost certainly not what you want. You want to track the current selection as it changes.
So what you want is probably something like the following:
[myViewController bind:#"value" toObject:myArrayController withKeyPath:#"selection.isLocked" options:nil];

How should I remove all items from an NSTableView controlled by NSArrayController?

I'm using an NSArrayController, NSMutableArray and NSTableView to show a list of my own custom objects (although this question probably applies if you're just showing a list of vanilla NSString objects too).
At various points in time, I need to clear out my array and refresh the data from my data source. However, just calling removeAllObjects on my NSMutableArray object does not trigger the KVO updates, so the list on screen remains unchanged.
NSArrayController has no removeAllObjects method available, which seems really weird. (It does have addObject, which I use to add the objects, ensuring the KVO is triggered and the UI is updated.)
The cleanest way I've managed to cause this happen correctly is:
[self willChangeValueForKey:#"myArray"];
[myArray removeAllObjects];
[self didChangeValueForKey:#"myArray"];
...so I'm kind of having to do the KVO notification manually myself (this is in my test app class, that contains the myArray property, which is NSMutableArray, as mentioned.)
This seems wrong - is there a better way? From my googling it seems a few people are confused by the lack of removeAllObjects in NSArrayController, but haven't seen any better solutions.
I have seen this solution:
[self removeObjectsAtArrangedObjectIndexes:
[NSIndexSet indexSetWithIndexesInRange:
NSMakeRange(0, [[self arrangedObjects] count])]];
but this looks even more unpleasant to me. At least my solution is at least marginally self-documenting.
Did Apple not notice that sometimes people might want to empty a list control being managed via an NSArrayController object? This seems kind of obvious, so I think I must be missing something...
Aside: of course, if I add new items to the array (via NSArrayController), then this triggers a KVO update with the NSArrayController/NSTableView, but:
Sometimes I don't put any items in the list, because there are none. So you just see the old items.
This is a bit yucky anyway.
You don't remove items from a table view. It doesn't have any items—it just displays another object's items.
If you bound the array controller's content array binding to an array property of some other object, then you should be working with that property of that object. Use [[object mutableArrayValueForKey:#"property"] removeAllObjects].
If, on the other hand, you haven't bound the array controller's content array binding, then you need to interact with its content directly. Use [[arrayController mutableArrayValueForKey:#"content"] removeAllObjects]. (You could also work with arrangedObjects instead of content. If one doesn't work, try the other—I've only ever done things the first way, binding the array controller to something else.)
Had this problem as well and solved it this way:
NSArrayController* persons = /* your array controller */;
[[persons content] removeAllObjects];
Swift
#IBOutlet var acLogs: NSArrayController!
acLogs.removeObjects(acLogs.content as! [AnyObject])
worked for me.
Solution in Swift:
if let ac = arrayController
{
let range:NSRange = NSMakeRange(0, ac.arrangedObjects.count);
let indexSet:NSIndexSet = NSIndexSet(indexesInRange: range);
ac.removeObjectsAtArrangedObjectIndexes(indexSet);
}
Just an update that works in Swift 4:
let range = 0 ..< (self.arrayController.arrangedObjects as AnyObject).count
self.arrayController.remove(atArrangedObjectIndexes: IndexSet(integersIn: range))

Resources