A text field’s value can be bound to an array controller using the Controller Key arrangedObjects and then one can have a Model Key Path such as @sum.aColumnKey or @max.aColumnKey etcetera. I want to customize the Controller Key arrangedObjects to become something like:
myArrangedObjects = arrangedObjects whose first column is a certain value.
According to the documentation, it appears that all these controller keys (such as arrangedObjects or selectedObjects) are methods of NSArrayController (or inherited), but they all clearly spell out that “This property is observable using key-value observing”, and I suspect that is necessary to make it work as a Controller Key.
If I make my own myArrangedObjects method in a subclassed array controller, it will not show up as an available controller key in IB, most probably become it is not “observable using key-value observing”.
What would I need to do to make it so? Can it be done within an AppleScript file, or can it only be done in “pure” Objective-C?
I checked out Key-Value Coding Programming Guide, but it’s a bit hard to digest. There are many requirements for KVC-compliance, but some of them, I believe, are already implemented for the class, whereas others might be irrelevant for my needs.
So, what particular things do I need to implement for my own myArrangedObjects?
Ok, thanks for comment. I would prefer not to override arrangeObjects: since in some cases I want the default arrangeObjects: but for other cases I want my own.
Yes, I could set the predicate, but I don’t want the window to change so as to show only the filtered objects, i.e the table should display all objects (all table rows) but my value I obtain must be based on a subset of those rows.
Hmm… didn’t even think of that, since I was focused on the array controller rather than looking at filtering an obtained NSArray. For my primary purpose, however, I don’t think this option is useful, since I don’t think it can be used to dynamically show current values in a text field (I think only the the array controller can do that efficiently by binding the text field value to a controller key and a model key path). Otherwise it’s an excellent idea I’ll keep in mind.
I might need to re-think what I show and how users might perceive that. As shane says, it’s probably faster to call setFilterPredicate than trying to override arrangeObjects (which implies a loop), or trying to make my own key-value compliant controller key. It will, however, definitely affect the design of the app and what users should be able to see and do, but maybe it is a viable route.
Hmm…, I have now experimented some, but I think I need to digest these options for a while… (right now it looks like the setFilterPredicate option is the most attractive one despite it requires some changes to the design and a slightly different user experience). I guess the speed of using filteredArrayUsingPredicate would be similar to the speed of setFilterPredicate and getting the array from the array controller on a largish some thousand rows table(?)
Sure it can. I’m not at my home computer now, so I can’t look up all the options, but you could put the filteredArrayUsingPredicate method inside one of the table’s delegate methods that is called when the content changes and bind the array you get back to your text field. You could also put it inside a setter method for your array which would be called when the array is updated by the array controller.
I think the speed of filtering with a predicate is very fast, and shouldn’t be a problem with a thousand row table.
After Edit: I do think that Shane’s suggestion of a second array controller is probably the easiest way to go though. I tried that with one of my old programs, and with a 5,000 row table I got instantaneous update of a second table that was bound to the filtered array of a second array controller. This way you only need one line of code to define your predicate, and the rest is done with bindings in IB.
Thought I’d just write a few follow-up comments:
Thanks both Shane and Ric for good useful proposals that gave me new ideas that I haven’t thought about before. I have been away a few days from programming, but have now tested the different methods and evaluated the pros and cons.
As Ric notes, using a second array controller is probably the best way – I really find that method to be a very neat flexible and elegant one! There was a problem though in that it wouldn’t update automatically after some types of table changes (such as clicking checkboxes) unless I have that array controller set to Auto Rearrange Content. But I don’t want auto rearrange since that makes some table changes very slow on a largish table (such as setting a checkbox on 500 rows in batch, which takes 2 seconds without auto rearrange, but 5 seconds with auto rearrange). It turns out to be faster (2 instead of 5 seconds) if I explicitly call rearrangeObjects when needed rather than using auto rearrange.
Creating a second array using filteredArrayUsingPredicate instead, called inside one of the table’s delegate methods, seems to have the same performance as using a second array controller.
Another table edit that can be very slow is paste: A rather extreme case is pasting 500 rows after 500 existing rows (to become 1000) takes 13 seconds on a few years old laptop, and it becomes longer if I have several bound computations on the array (such as “@max.aColumnKey” for a column of dates). I have four such computations on the second array controller to get the earliest and latest in two date stamp columns (plus two sum calculations on the original array controller), so performance does matter to me. The two methods do seem to be very similar in this respect though.
Elegance and ease of use favors the method using a second array controller. It does have other merits in cases of empty results or no rows, where no extra coding is needed to take care of such special cases. For “normal” edits there are hardly any delays on a thousand row table.