NSArraycontroller access of content - objective-c

I am connecting an arraycontroller (myArraycontroller) in Interface Builder to an array (fileDictionariesArray) consisting of dictionaries. This works fine, but when I try to access and enumerate over the contents of the arraycontroller [myArrayController arrangedObjects] I get nil in content until I add the content programmatically to my arrayController like this:
[myArrayController addObjects:fileDictionariesArray];
After I have done that I can loop over all the contents. The problem is that the array (fileDictionariesArray) has been added two times. One time through IB binding and one time through addObjects. I just can't access the arrayContoller until I set the content programmatically. I can delete the content of the arrayController and then set it programmatically again like this:
[myArrayController setContent:nil];
[myArrayController addObjects:fileDictionariesArray];
Which gives the correct number of items in the arrayController, but it does not seem like the correct way to do this. I would appreciate it of anyone could give me a hint on how to access my arrayController through the arrangedObjects array without adding the content two times.
Thank you for your help. Cheers, Trond Kristiansen

Based on your post and the comments between you and Bavarious, I'm convinced he shouldn't have deleted his answer. He'd asked you where you were calling this code (from -awakeFromNib?). This is an essential question to answer.
If you call this from -awakeFromNib, the next question is "of what object in the nib? the owner? some other controller? what is the? a document?"
The thing is the array controller likely doesn't have time to observe the "changes" to the array before you call your code. That's why adding the content forcibly results in its being added twice (because after you're done, the array controller observes its content array and pulls in the content ... again ... at which point you've doubled the array's contents).
If you're doing immediate startup stuff, it might be best to operate directly on the array. There's nothing "wrong" or "dirty" about that, so long as you leave responsibility with the array controller after startup is complete.
It's hard to get any more specific because I'm just guessing at what you might be doing. Amend your question to include a more complete description of how/where your array controller fits into the architecture, what specifically you're doing to the array at startup, etc. Missing any of this information forces too many guesses.

Related

Issue in lazy loading the table view

I am badly struck in a issue where I am trying to populate the nestableview lazily. Below is my approach.
I have created a custom class PRIList where it has an instance of array to manage the models.
I have bound the priList.items to the array controller in the xib where items is not an instance in PRIList but to support lazy loading I have implemented the methods countOfItems and objectInItemsAtIndex:.
Initialy when I populate the PRIList I populate few objects (say 50) with valid objects and rest with the faulty objects. In the objectInItemsAtIndex I check if the item at particular index is valid or faulty. If it is faulty I fetch next set of 50 objects.
What I understand is NSArrayController calls the method objectInItemsAtIndex for only the visible rows in the table view. But the problem here is as soon as set the PRIList the objectInItemsAtIndex method is called for all the objects. This is even called when some selection is changed in table view (the stack trace shows this method is called from [_NSModelObservingTracker startObservingModelObjectAtReferenceIndex])
Basically I want to fetch the records whenever the user scrolls down in the table view.
I followed the same approach in a different project in Lion. It worked there. Currently I am in Mavericks.
I tried overriding the isCompatibleWithResponsiveScrolling in the custom table view and returned it to NO. Still no luck.
Any help is very much appreciated.
First, have you assigned or bound the sort descriptors of the array controller? Or set any columns to automatically generate sort descriptors? (I'm not sure that latter is relevant. It depends on whether the column is sorted by default.)
In any case, if the array controller feels the need to sort the objects in order to arrange the objects, then it will need to load all of the contents. I was under the impression that it always does so, anyway, although you report that it works.
For an issue like this, I'd recommend that you go for full manual control. That means not using bindings or an array controller. Use a data source.

Bind custom control to NSArrayController

I've subclassed NSTextField to create a custom control, and I want to bind a property (which is an NSArray) of my custom control to an NSArrayController. However I don't know how to propagate the array from my control to the NSArrayController. The key-path I'm using on the NSArrayController is arrangedObjects.name. For example, if I'm trying to propagate the array (#"One", #"Two", #"Three") and I simply use:
[boundObject setValue:myArray forKeyPath:#"arrangedObjects.name"]
it will set the value of each element of arrangedObjects.name to the array (#"One", #"Two", #"Three"). What I want to happen is to have the first element in arrangedObjects.name set to #"One", the second value set to #"Two", etc.
NSTableColumn does this, so I know it's possible, but I can't figure out how it is implemented.
What's the best way to achieve this?
Ok, well I think I may have answered my own question. I won't mark it as the correct answer right away in case someone can tell me a better way to do this. This way is pretty much a hack.
Anyway, I was reading about NSArrayController here: http://www.cocoadev.com/index.pl?NSArrayController which says:
If valueForKeyPath:#"arrangedObjects.name" is sent to an array controller, one (as expected) gets back an array of name values, but if another object replicates this behaviour, and the table column's value is instead bound to this object, it will display the entire array for each row.
and
You bind the value of an NSTableColumn to arrangedObjects.someKey. If you try to programmatically invoke valueForKeyPath:#"arrangedObjects.someKey" on your array controller, you'll get back the array resulting from invoking valueForKey:#"someKey" on the arrangedObjects array -- this is all fine. So one would think that NSTableColumn's value can also be bound to my objects someArray.someKey, but this will not work (on several levels).
So basically it sounds like NSTableColumn special cases its bindings for NSArrayController and arrangedObjects which is why it works the right way, and my custom control doesn't.
This doesn't seem very flexible, but it's the only way I can find to make it actually work. I implemented a special case in my bindings for NSArrayController and arrangedObjects and I was able to make it work like I wanted.
A better solution would be much appreciated, though!

IKImageBrowserView appears to not bind properly

my question relates to Interface Builder and an IKImageBrowserView not implementing bindings as I would expect.
I have a fairly long key path to get to an NSArrayController, the contents of which I want to display in the IKImageBrowserView.
This is the key path I am binding an NSTextField's Display Pattern binding to:
currentOrder.imagesArray.unvalidatedImages.arrangedObjects.#count of the AppDelegate.
This works fine and gives me the number of unvalidated items in the array of images belonging to the current order, which is what it is supposed to.
When I attempt to bind the following key path to the IKImageBrowserView's content: currentOrder.imagesArray.unvalidatedImages.arrangedObjects of the AppDelegate, Interface Builder gives no error but instead acts as if I hadn't entered anything. When I click back to the inspector, the bindings key path is blank again.
It does however log the following to the Console:
Ignoring exception related to working with bindings: NSUnknownKeyException, [<NSCustomObject> addObserver:<IKImageBrowserView ...> forKeyPath:#"currentOrder.imagesArray.unvalidatedImages.arrangedObjects" ...] was sent to an object that is not KVC-compliant for the "currentOrder" property.
My AppDelegate implements currentOrder as an #property retained and all sub-keys are also #properties. I can be sure that these properties are KVC-okay because the NSTextField above is able to read changes without a problem. Interestingly enough the IKImageBrowserView's selectionIndexes is able to bind to ...unvalidatedImages.selectionIndexes, it's only the content that can't.
I have implemented a workaround whereby I have placed an NSArrayController in my nib file and bound the Content Array to ...arrangedObjects then bound the IKImageBrowserView to the array controller but would be very happy to have a neater solution, or at least to know whether I am doing anything wrong.
Thank you!
Did you try implementing the methods of <IKImageBrowserDataSource> in your app delegate, and using the _dataSource connection instead of a binding? That works for me, and is how the Apple tutorial has you do it.
You basically only need to implement – numberOfItemsInImageBrowser: and – imageBrowser:itemAtIndex: to provide the data, which isn't too bad. The slightly trickier part is to implement an <IKImageBrowserItem> class to wrap your data, but even that doesn't require massive effort. The tutorial linked above should help a lot.

setSelectsInsertedObjects on NSArrayController not actually selecting

I Have an NSArrayController bound to a NSUserDefaults controller, with setSelectsInsertedObjects set to YES in Interface Builder, but when I click Add, the previously select object gets unselected, instead of selecting the newly added object.
What am I missing?
How are you binding them? If it is through NSArrayController's 'content' binding, then I believe it tries to bind the selectionIndexes to the same object. This class (NSIndexSet) does not work with NSUserDefaults (I have no idea why, but I've had the same problem in the past - I think it has something to do with it's object lifecycle; it gets initialized as empty and then adds indexes or something). What setSelectsInsertedObjects is doing is just automatically updating the selectionIndexes when a new object is added, and basically your NSUserDefaults controller is messing that up. I'm not sure where it is, but I think if you hunt around NSArrayController's bindings you will find one for selectionIndexes (or something related) that was automatically bound to NSUserDefaults for you; if you uncheck that, things should work.
That's pretty much what selectsInsertedObjects means, as I understand it. When the user adds a new item, the new item is selected, replacing the previous selection.
If you want different behavior, you could extend NSArrayController or create your own controller class that uses NSArrayController as a delegate, perhaps based on NSProxy. I believe you'd need to override add: to:
save the current selection
call the parent add:
merge the current selection with the saved selection
set the selection to the merged selection
However, I don't know enough about NSArrayController internals to say whether this would work.

Cocoa: getting a Table View cell to send action messages

I'm really having trouble getting a Cocoa Table View cell to send action messages.
At the most basic level, in IB there is an action assigned for the NSTextViewCell object, and after editing and pressing Return nothing happens.
So I have an IBOutlet hooked up to the NSTextViewCell, and have been experimenting with NSActionCell messages to it. But the Table View seems to pretty much just ignore them.
I've also tried subclassing NSTextViewCell, but the methods I'm seeing all look like they want to pass values to the object from somewhere, not return a value from inside the object to configure its behavior.
I'm pretty new to programming and Cocoa -- can someone explain each thing that needs to be overridden and how and where to do it?
AFAIK, the cells in an NSTableView won't send action messages out to your application, they're sent to the NSTableView so it can update its data. NSTableView itself tries to be pretty clever and update your data directly, rather than just telling you something changed, so depending on what you're trying to do and what the data source for the table is, you have a few options.
If you're using an NSTableViewDataSource object to populate the table, it's simple; just implement tableView:setObjectValue:forTableColumn:row: and the NSTableView will call that every time something is edited.
If you're using Cocoa data binding (for example, using an NSArrayController to bind an array of objects to the table,) then as long as everything is wired up correctly, the data should just automagically get updated in the source objects when the table is edited. If you need to take special action, then you can do whatever you need to in the property setter of your data class.
I haven't tried it yet, but could work...
NSCell *cellYouWant = [tableView preparedCellAtColumn:tableView.clickedColumn row:tableView.clickedRow];