NSCollectionView to display a remote collection : part 4

Handling the selection of the collection view.

Because the prototype view is custom made, we have to implement the visual feedback to the user when an item is selected.

First set the collection view as multiple selectable in the NIB file. Below is the place where that is changed in interface builder :

collectionview_selection

Then, in the prototype’s view class (a subclass of NSView) add a property named ‘selected’. In the draw method draw a background according to the state of this variable.

- (void)drawRect:(NSRect)dirtyRect{
    if ([self selected]) {
        [[NSColor grayColor] set];
        NSRectFill([selfbounds]);
    }
    [super drawRect:dirtyRect];
    // Drawing code here.
}

In the CollectionViewItem subclass override the setSelected method that the NSCollectionView UI control will be calling when the user makes a selection or a deselection

- (void) setSelected:(BOOL)selected {
    [super setSelected:selected];
    [(RemovePhotosImageItemView*)[selfview] setSelected: selected];
    [(RemovePhotosImageItemView*)[selfview] setNeedsDisplay: TRUE];
}

Now, when a selection is made or unmade in the UI the item will either draw a background or not (respectively).

NSCollectionView to display a remote collection : part 3

continuing …

The item prototype will have a view defined in it’s own NIB file. For this example I have defined a view that has an image well and a label. On top of the image well is a circular progress indicator. The image well is hidden in the NIB file so that the progress indicator is shown when the nib is first loaded.

The item prototype class is a subclass of NSCollectionViewItem. We define this class in our project and define outlets for the controls we want to manipulate.

@interface RemovePhotosCollectionViewItem : NSCollectionViewItem
@property IBOutlet NSProgressIndicator* progressIndicator;
@property BOOL imageLoaded;- (void) loadImage;
@end

Then create a new NIB file that defines the item prototype’s view.  Set the file’s owner to be the subclass of the NSCollectionViewItem defined above. This subclass already has some outlets defined in the superclass that are suitable to our example namely the imageView and textField outlets.

collectionview_item_connections

In the above image a binding can be see below the connections. We will bind the item view’s controls to the prototypical object that the view represents. The binding to the image well is shown below :

imagewell_binding

The NSCollectionViewItem has a representedObject property that can be used as the key path to the properties of the element prototype. (The class RemovePhotosCollectionViewItem)

We still have to go into the main NIB and bind the NSCollectionView to the array controller as shown below :

NSCollectionView_binding

The ‘arrangedObjects’ path refers to the ArrayController defined in the main NIB.

In the Collection View Item subclass we do two things. Start the circular progress indicator animation to give feedback to the user that the data is loading, and trigger a call to the server to load the image.

The imageLoaded property marks the loading call as terminated and we manage that in the view item code. First we set it to false in -awakeFromNib.

- (void) awakeFromNib {
    [self setImageLoaded: FALSE];
}

Then we need a way to trigger the loading process. Since we access the represented object (the underlying ImageBrowserImage object stored in the array on the main view’s controller) we can only  access it after the bindings are established by the cocoa framework. We trigger the loading process by overriding the “setRepresentedObject” message as below :

- (void) setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];
    if (nil != representedObject && ![self imageLoaded]) {        [NSThreaddetachNewThreadSelector: @selector(loadImage) toTarget:selfwithObject:nil];
        [self setImageLoaded: TRUE];
}
}

In the loadImage method defined in the item view’s class we synchronously call the server in another thread, that does not block the user interface. When the image is loaded we set the attributes on the represented object that triggers the display of the data in the user interface.

ImageBrowserImage* imageDataObj = (ImageBrowserImage*)[selfrepresentedObject];
// ... load the image from the server        [imageDataObj setImageKey:imageKey];        [imageDataObj setImageData: decodedImageData];                [[self progressIndicator] setHidden: TRUE];        [[selfimageView] setHidden: FALSE];

As can be seen in the above code we swap the visibility of the image well and the progress indicator when the data is loaded.

That’s it.

NSCollectionView to display a remote collection : part 2

Post with 2nd part of this subject.

First drag a Collection View from the palette in Xcode into a window. This will create three new objects in the NIB file :

  • The collection view object
  • The collection view item prototype
  • The collection view item prototype’s view

The IDE automatically creates a view for the prototype, but we will have a NIB file dedicated to this view.

In this example we will have an array of objects that hold an image and a text label. The code that follows is the declaration of the model object :

@interface ImageBrowserImage : NSObject
@property NSUInteger imageIndex;
@property NSString* queryKey;
@property (strong) NSString* imageKey;
@property (strong) NSData* imageData;
@property ServerAPI* serverAPI;
@end

Next, in the controller class that manages the window with the collection view declare a property as an NSMutableArray :

@property NSMutableArray* imageBrowserArray;

And add the necessary methods to make the above array KVO compliant.

- (void)insertObject:(ImageBrowserImage *)p inImageBrowserArrayAtIndex:(NSUInteger)index;
- (void)removeObjectFromImageBrowserArrayAtIndex:(NSUInteger)index;

Now create a NSArrayController in the main NIB file.

main_nib_objects In the array controller’s attributes inspector set the Object controller as the following :

arraycontroller_attrinspector

We set the mode of the object controller’s to class and specify the item class. We then specify which of the item classe’s properties will be exposed to the view layer.

Now we will bind the array declared in the main view’s controller to the NSArrayController we just added to the main NIB. In the NSArrayController’s bindings inspector set the ‘Bind To’ drop down to the main view’s controller class (which must be declared in the NIB). The model key path is the name of the KVC compliant  mutable array property declared in the bound class.

arraycontroller_binding

In the next post we will create the item prototype NIB file and configure its outlets.

NSCollectionView to display a remote collection : part 1

I wanted to display a large number of images stored on a server in a cocoa desktop app. My first approach was to use the class IKImageBrowserView.

But this class does not allow customisation of the collection item. One good thing it does is that it’s able to lazy load the collection elements (that are images) so the user interface remains responsive even when the collection’s size is very large.

Wanting to have both item customisation and lazy loading, I implemented a collection view with an item prototype that displays a circular progress indicator while it is loading the data and shows an image and some other controls afterwards.

All the collection’s elements are created immediately but the bulk of the data is loaded in the background with the use of one thread per collection element. Each collection item triggers this thread that loads the data from the server.

One thing to note is that on the server side, a query’s results are persisted on a database, so the client (cocoa app) can obtain the collection element at an index for a given server side query.

The collection size is known in advance, and while the cocoa view displays its elements it is assumed that the collection does not change.

In the next posts I will go into the details of this setup.