Bindings

Aperture Plugin: Implementing A Table Of Images

cocoasmall
At this point in the project I decided two things: that I was going to release the plugin as 1.0 in a month, and that I needed to change the interface. I had already switched from a vertical arrangement with the prefix on top, to a horizontal arrangement with the prefix on the left. The trigger for this next change was the unsatisfactory display of the example random filename and the need to distinguish between what happens to the random naming when the freeze feature is used and is not.

The final version I decided to go with features a table that shows a thumbnail, plus the image version name, the image caption, and the new random name. This is the clearest way to show what is going to happen: show it happening.
rw1.0mm
By using the propertiesWithoutThumbnailForImageAtIndex: and thumbnailForImageAtIndex: methods I can get the data I need from Aperture for each table entry. So my plan was to bind the table to an array controller and bind the array controller to an array in the Random_Wok object that contained all the thumbnails and text. Updating the thumbnails and text would cause changes to propagate through the controller to the view as needed.

So I added an array controller to the nib and called it ImageTable:
rwok305
I then bound it to the model like this:
rwok306
and set up like this:
rwok307
The model key is imageTableData and I use the keys thumb and text to access the data for the columns. So to support the needs of the array controller I implement an array called imageTableData as an ivar in Random_Wok and fill it with dictionaries with keys thumb and text, corresponding to NSImage and NSString objects I want to display.

I set up the table view like this:
rwok300
Notice that I actually used a custom class for this. More on that later. The first (image) column is set up like this:
rwok301
To display the image, I drag in an image cell. The properties of the image cell is accessed via the small triangle top right:
rwok302
The inspector shows it as an NSImageCell, set up this way:
rwok303
Now onto the bindings. The first column is bound to the array controller and uses the thumb key to get data from the model:
rwok304
The second column is set up this way:
rwok308
And bound to the text key through the array controller like this:
rwok309

As I discovered while attempting to bind each table column to a separate array, setting up table column bindings also automatically sets up the the table bindings. This makes it impossible to bind separate columns to separate array objects: they have to all go to one object and then use the key path to get data from separate places. This pretty much means that you need an array of dictionaries to drive a table.

To provide data for the model, I loaded all the thumbnails and text into the array during the plugin initialization. Through the bindings I was able to change the dictionary contents and have the table update automatically. This all worked fine for a small selection of images.

However if I selected 500 images, the array would take a long time to fill. Worse, this was happening before anything was displayed (since the array controller was set to prepare content), making it look like the plugin has frozen. And changes to the text caused by changing the parameters for the random file names were also very slow because they too would be performed 500 times on all the items in the array.

So another approach was needed.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Displaying The Image Count With Bindings

cocoasmall
To give some feedback to the user I want to include a display of the number of images that will be exported. I can do that by using bindings: by binding text on the window to an ivar in my Random_Wok object.

I create an ivar called imagesToProcess and an accessor to set it:
rwok207
In -willBeActivated, I add some code to set it up before it is used:
rwok209
And since changes in the image type (master or version) can change the number of images, I have to set it each time the export type changes:
rwok210
That is all the code except for this method:
rwok208
It returns "s" if imagesToProcess is more than one, otherwise an empty string. I need that in order to implement correct pluralization of my display string.

To display the image count on the window I add an NSTextField in the corner like this:
rwok211
Its value is unimportant because I will be constructing it dynamically with bindings, but it helps to have a descriptive string there. I set up its bindings like this:
rwok212
The Display Pattern string is what does the magic. Value1 and Value2 are bound to different key paths. The first to the value given by imagesToProcess, and the second to the plural string given by pluralImagesToProcess:
rwok213
When these are substituted into the Display Pattern string, the result is what the user needs to see how many images are selected:
rwok214

Because I implemented the accessors and I use them to change the value, bindings take care of doing this display updates automatically. If I select images in Aperture that include some with multiple versions of one master, the image count displayed changes when I click Master and then Version, just as it should.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Using Bindings To Populate The Length Pop-Up

cocoasmall
To populate the length pop-up I'm going to use bindings. Bindings allow changes in one object to automatically control another object. In this case I want the contents of the length pop-up to reflect the values I store in an array in my Random_Wok instance. Each time I change the array contents, the pop-up contents change. The advantage to this is that I don't have to write the code that populates the pop-up.

To make this work I need a third object: an array controller instance in my nib. An array controller is a pre-written, general-purpose controller that knows how to communicate with objects that expose bindings. It's like hiring a manager to run a department. Since it knows about arrays it can take a lot of work off my hands by dealing with the individual array elements for me. The array controller will mediate between changes in my data model and updates to the view.

To get an array controller instance into my nib I drag one from the controller palette to the nib file:
rwok69
And I give it a name by double-clicking it: Length Popup Controller. To make the controller do anything useful I have to hook it up to a data model (an array) and a view (the pop-up). The controller then mediates between these for me.

The first thing I have to do is tell the controller what kind of object is in the array it is dealing with. "Good morning new manager, your staff consists of interns". In this case it is an array of NSMutableStrings that I will be modifying according to which string lengths I want the user to be able to choose:
rwok70
Then I have to tell the array controller which array it will be dealing with. "Your interns are in that cube farm over there". To do this I bind the array controllers content array to the array of length strings in my Random_Wok object. That's an ivar called _lengthStrings:
rwok71
I bind to File's Owner because that is a stand-in for my Random_Wok class. What this step does is to make the controller able to observe changes to the _lengthStrings array. The observation is incomplete at this stage -- the manager knows which cube farm to keep an eye on, but none of the interns yet know to tell the manager that anything has happened.

Next I have to tell the pop-up button that its contents will be supplied by an array controller. To do that I select the pop-up button and bind it to the array controller. The pop-up button has a binding called content that is designed for doing just this:
rwok72
The controller key (arrangedObjects) is what the pop-up will observe to get its content. "Catering department, watch the manager's list of foods on his white board. Any time he changes it update the menu".

The last thing I must do is to add code to Random_Wok so that the object tells the NSArrayController that something has changed (remember that the interns in their cubes don't automatically tell the manager that their choice of foods has been updated). I code manual observer notification for this by using -willChangeValueForKey: and -didChangeValueForKey:
rwok73
I need this manual method of notifying the controller that something has happened because my Random_Wok class is not Key Value Observing compliant. As a class it lacks the methods that the KVO mechanism requires to have change notifications automatically sent to observers. The array controller is KVO compliant, to the pop-up can automatically get notifications that the content array has changed.

Now the interns are saying to the manager "We will have a new list of foods in a minute -- OK, it's been updated". And the manager knows to update his whiteboard, the caterers see the new list of foods because they are watching the whiteboard, and the menu gets changed. And all along the interns don't know or care why they are creating food lists, and the caterers don't know or care where the food lists are coming from.

I try out the code and it works:
rwok74
The final code for actually creating the array was more complicated than the little test I initially wrote:
rwok75

This includes some extra logic to select the current length, or if not possible, use the highest length available. In this way as the user clicks around the available controls the pop-up does not keep resetting to the minimum length setting.

The other parts of this series can be found via the Cocoa page.
|
The Bagelturf site welcomes Donations of any size