Browsing Images
Applications such as iPhoto provide a rich user experience for viewing digital image collections. By using the IKImageBrowserView
class and its related protocols—IKImageBrowserDataSource
, IKImageBrowserDelegate
, and IKImageBrowserItem
—any application can support browsing large numbers of images efficiently.
This chapter describes the user interface provided by the IKImageBrowserView
class, discusses the related protocols, lists the image representation types that the browser can display, and includes step-by-step instructions for creating an application that uses the image browser.
The Image Browser User Interface
The IKImageBrowserView
class provides a view that displays an array of images. The size of each image depends on the zoom value that you set for the image browser view. Typically, an application provides a control, such as the slider shown in Figure 3-1, that allows the user to control the zoom value.
When fully zoomed in, the image browser displays a single image, as shown in Figure 3-2.
You can set up the image browser view to support dragging images into the window to add them as well as dragging images within the window to rearrange them. In addition to drag and drop, your application should support adding photos through the NSOpenPanel
class, as shown in Figure 3-3.
The IKImageBrowserView
class also supports scrolling (see Figure 3-2), which is necessary for large image sets or when the user has the zoom value set such that the image set doesn’t fit in the view. Your application needs to take one of the following approaches:
Embed the image browser in an
NSScrollView
object, which is the typical approach.Connect the image browser to an
NSScroller
object. This approach is a bit more efficient but doesn’t allow auto-hiding of the scrollers.
The Requirements of an Image Browser Application
This section gives an overview of the Image Kit classes and protocols needed to write a full-featured image browser application and describes how such an application works. After reading this section you’ll be ready to read the rest of this chapter, which provides step-by-step instructions for these programming tasks:
The Image Kit Classes and Protocols for an Image Browser Application
An image browser application has a number of requirements. In addition to creating an instance of the IKImageBrowserView
class, you’ll need to provide a data source that provides the items to show in the image browser. You’ll also need to implement the required methods of the IKImageBrowserDataSource
and IKImageBrowserItem
protocols.
The IKImageBrowserDataSource
protocol defines methods for accessing the data source. You use this protocol to add and remove items, to move items, and to obtain information about individual items or groups. Your application must implement two methods:
numberOfItemsInImageBrowser:
returns the number of items in the image browser.imageBrowser:itemAtIndex:
returns the object located at a specified index. The returned object must implement the required methods of theIKImageBrowserItem
protocol.
Methods that support editing or that return group information are optional. See IKImageBrowserDataSource Protocol Reference for details.
The IKImageBrowserItem
protocol defines required and optional methods that the browser view uses to access a particular item from the data source. You must implement three methods:
imageRepresentationType
returns the image representation type. An image browser view can handle a variety of image representations. You must specify which representation type the image browser view uses by returning the appropriate constant. Table 3-1 lists the constants.imageUID
returns a string that uniquely identifies the data source item, such as the string that represents the path to the item.imageRepresentation
returns the representation (such as the path to an image) for an item in the image browser view. The representation must match the image representation type.
You can optionally implement methods that return the image version, title, subtitle, and whether or not the item can be selected. See IKImageBrowserItem Protocol Reference for details.
Image representation constants | Image representation data types |
---|---|
| A path ( |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| A path ( |
|
|
| A path ( |
| A path ( |
|
|
| A path ( |
The IKImageBrowserDelegate
informal protocol defines methods that respond to user events, such as a selection. See IKImageBrowserDelegate Protocol Reference for a description of the delegate methods.
How the Sample Image Browser Application Works
When the sample application that you’ll build in the rest of this chapter launches, it allocates two mutable arrays, one that represents images that are displayed in the image browser view (image array) and another that represents images that need to be imported into the image browser view (import images array). Initially, both arrays are empty. The application then performs any setup work for the image browser view, such as setting the style of the cells and whether the movement of items is animated. After the setup is complete, the window with the image browser view opens without any images. The image browser view has a vertical scroll bar. The window has a button for importing images.
Clicking the Import Images button invokes an action to add images to the image browser view. The application uses an instance of the NSOpenPanel
class to solicit a selection from the user. If the user makes a selection, the Open panel returns the selection as a path. This application allows users to choose either a file or a folder. A folder can contain files, other folders, or both files and folders. However, files are the only items that are added to the image browser view, which means that the application must traverse all paths until each resolves to a single file.
The application creates a data source object—an image object—for each file. The object has one instance variable, the path to the file (represented as an NSString
object). Each image object is appended to the import images array. After all the objects are added to that array, the application appends that array to the images array. The application then removes all objects from the import images array so that the array can be ready to import more images should the user choose to do so. Finally the application calls the reloadData
method of the IKImageBrowserView
class to populate the image browser view with the images represented by the import images array.
The image browser application has five major parts to its implementation:
The nib file. You need to add an Image Browser view (
IKImageBrowserView
class) to a window. You’ll add a scroll view for the vertical scroller, and a button for importing images.The window controller. This is the main class. You create it in Xcode and then instantiate it in Interface Builder. The window controller is the data source of the image browser view. The controller manages the mutable array for the image objects that represent the items in the image browser view and the mutable array for imported image objects. In Interface Builder, you’ll need to make the appropriate connections between the image browser view and the window controller.
The
IKImageBrowserDataSource
protocol. You need to implement the required methods for this protocol:numberOfItemsInImageBrowser:
andimageBrowser:itemAtIndex:
.The
IKImageBrowserItem
protocol. You need to declare an interface for an image object that represents a single item in the image browser view. In the implementation for the image object, you need to provide the required methods for this protocol:imageUID
,imageRepresentationType
, andimageRepresentation
.Image importing. You need to set up an Open panel and write code that traverses all folder paths until all paths are resolved to individual items.
The sections that follow provide step-by-step instructions for writing the image browser application.
Displaying Images in an Image Browser
This section shows how to create an application that displays images in an image browser. First, you’ll set up the Xcode project, the project files, and the interface for the image browser controller. Then you’ll add routines to the implementation. Finally, you’ll set up the user interface in Interface Builder.
Setting Up the Project, Project Files, and the Controller Interface
Follow these steps to set up the project:
Open Xcode and create a Cocoa application named
Browse Images
.Add the Quartz framework.
For more details, seeUsing the Image Kit in Xcode.
Choose File > New File.
Choose “Objective-C Class NSWindowController subclass” and click Next.
Name the file
ImageBrowserController.m
and keep the option to create the header file. Then click Finish.In the
ImageBrowserController.h
file, import the Quartz framework by adding this statement:#import <Quartz/Quartz.h>
Add
IBOutlet
outlet for the image browser and two mutable arrays .One array will hold paths to images that are currently displayed in the browser. The other array will hold paths to images that are about to be imported into the browser.
@interface ImageBrowserController : NSWindowController {
IBOutlet id mImageBrowser;
NSMutableArray * mImages;
NSMutableArray * mImportedImages;
}
@end
Add the following method signature:
- (IBAction) addImageButtonClicked:(id) sender;
Save the
ImageBrowserController.h
file.
Adding Routines to the Implementation File
Follow these steps to implement the main tasks of the image browser application:
Open the
ImageBrowserController.m
file.In the implementation file, add an
awakeFromNib
method.This method allocates and initializes the images and imported images arrays. For visual interest, the method also sets the image browser to animate as it updates.
- (void) awakeFromNib
{
mImages = [[NSMutableArray alloc] init];
mImportedImages = [[NSMutableArray alloc] init];
[mImageBrowser setAnimates:YES];
}
Add a method for updating the data source for the image browser controller.
This method needs to add the recently imported items to the image objects array, then empty the imported images array. Finally it reloads the image browser, which causes the image browser to update the display with the recently added images.
- (void) updateDatasource
{
[mImages addObjectsFromArray:mImportedImages];
[mImportedImages removeAllObjects];
[mImageBrowser reloadData];
}
Implement the two required methods of the image browser data source protocol.
The number of items in the image browser is simply the number of items in the images array. An item’s index is simply its index in the images array.
- (int) numberOfItemsInImageBrowser:(IKImageBrowserView *) view
{
return [mImages count];
}
- (id) imageBrowser:(IKImageBrowserView *) view itemAtIndex:(int) index
{
return [mImages objectAtIndex:index];
}
In the
IKImageBrowserController.m
file, immediately after the import statement, create an interface for a data source object.The data source object defines one instance variable, a string that is a path to an item to display.
@interface MyImageObject : NSObject{
NSString * mPath;
}
@end
In the same file, create the implementation for the data source object
MyImageObject
. In the next three steps, you’ll be entering methods between the the following two statements, as you would normally for a Cocoa class.@implementation MyImageObject
// Methods here
@end
Write a method that sets the path object.
- (void) setPath:(NSString *) path
{
if(mPath != path){
mPath = path;
}
}
Implement the three required methods of the
IKImageBrowserItem
protocol.The data source is a file path representation and its unique identifier is the path itself.
- (NSString *) imageRepresentationType
{
return IKImageBrowserPathRepresentationType;
}
- (id) imageRepresentation
{
return mPath;
}
- (NSString *) imageUID
{
return mPath;
}
Now that you are done with the MyImageObject implementation, you need to add a method to the ImageBrowserController implementation. This method is invoked when the user clicks an Import Images button.
This method calls a routine that displays the Open panel and returns the path chosen by the user. If there is a path, the method sets up an independent thread, calling the
addImagesWithPaths:
method, which you’ll write next. You’ll write an open files routine later. Check to make sure that you add the following method in the ImageBrowserController implementation,- (IBAction) addImageButtonClicked:(id) sender
{
NSArray *path = openFiles();
if(!path){
NSLog(@"No path selected, return...");
return;
}
[NSThread detachNewThreadSelector:@selector(addImagesWithPaths:) toTarget:self withObject:path];
}
Write a method that adds an array of paths to the data source.
The method parses all paths in the
paths
array and adds them to a temporary array. (You’ll write theaddImagesWithPath:
method in the next step. Note this is path, singular.) It then updates the data source in the main thread.Add this method after the
addImageButtonClicked:
method.- (void) addImagesWithPaths:(NSArray *) paths
{
int i, n;
n = [paths count];
for(i=0; i<n; i++){
NSString *path = [paths objectAtIndex:i];
[self addImagesWithPath:path recursive:NO];
}
[self performSelectorOnMainThread:@selector(updateDatasource)
withObject:nil
waitUntilDone:YES];
}
Write the method that adds images at a path.
This method checks to see if the path identifies a directory or a file. If the path is a directory, the code parses the directory content and calls the
addImagesAtPath:
method. If the path is to a file, the code calls a method that adds a single image to the imported images array. You’ll write theaddAnImageWithPath:
method next.Note this method has an option to enable or disable recursion. Enabling recursion allows you to traverse nested folders to retrieve the individual items in each folder.
Add this method before the
addImagesWithPaths:
method.- (void) addImagesWithPath:(NSString *) path recursive:(BOOL) recursive
{
int i, n;
BOOL dir;
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&dir];
if(dir){
NSArray *content = [[NSFileManager defaultManager]
directoryContentsAtPath:path];
n = [content count];
for(i=0; i<n; i++){
if(recursive)
[self addImagesWithPath:
[path stringByAppendingPathComponent:
[content objectAtIndex:i]]
recursive:NO];
else
[self addAnImageWithPath:
[path stringByAppendingPathComponent:
[content objectAtIndex:i]]];
}
}
else
[self addAnImageWithPath:path];
}
Write a method that adds a single image to the imported images array.
The method creates an image object and adds the path to the object. The code then adds the image object to the imported images array.
Add this method before the
addImagesWithPath:recursive:
method.- (void) addAnImageWithPath:(NSString *) path
{
MyImageObject *p;
p = [[MyImageObject alloc] init];
[p setPath:path];
[mImportedImages addObject:p];
}
Write an open files routine that displays an
NSOpenPanel
object and retrieves the path chosen by the user.Place this routine at the top of the
ImageBrowserController.m
file, immediately after the import statement.static NSArray *openFiles()
{
NSOpenPanel *panel;
panel = [NSOpenPanel openPanel];
[panel setFloatingPanel:YES];
[panel setCanChooseDirectories:YES];
[panel setCanChooseFiles:YES];
int i = [panel runModal];
if(i == NSOKButton){
return [panel URLs];
}
return nil;
}
Build the Project.
This ensures that Interface Builder detects the action that you added.
Save the
IKImageBrowserController.m
file.
Creating the User Interface
Set up the user interface in Interface Builder by following these steps:
Double-click the
MainMenu.nib
file to open Interface Builder.Choose File > Synchronize With Xcode.
If it’s not already open, double-click the Window icon in the nib document window.
Drag a Image Browser View from the Library to the window and resize the view so that you leave space at the bottom of the window for a button.
In the Size inspector for the view, make sure that the Autosizing springs and struts look as follows:
Choose Layout > Embed Objects In > Scroll View.
In the Scroll View Attributes inspector, leave Show Vertical Scroller selected but deselect Show Horizontal Scroller.
This will allow users to scroll easily through large numbers of images.
In the Size pane, set the Autosizing springs and struts so they look the same as those shown in Step 5.
Drag an Object (
NSObject
) from the Library to the nib document window.In the Identity inspector, choose ImageBrowserController from the Class pop-up menu.
Control-drag from the ImageBrowserController icon to the title bar of the window. Then connect to
window
in the connections panel.Control-drag from the ImageBrowserController icon to the
IKImageBrowserView
view. Then connect tomImageBrowser
in the connections panel.Control-drag from the
IKImageBrowserView
view to the ImageBrowserController icon. Then connect to _dataSource in the connections panel.Control-drag from the window icon to the controller icon. Then connect to
delegate
in the connections panel.Drag a Push Button from the library to the lower right portion of the window and label it Import Images.
In the Size inspector, set Autosizing to have outer struts on the left and bottom.
Control-drag from the Import Images button to the the ImageBrowserController icon and connect to the
addImageButtonClicked:
action in the connections panel.Save the nib file.
In Xcode, click Build and Go.
Click the Import Images button and make sure that the image browser works.
Supporting Zoom
Next you’ll add the ability for the user to zoom images. You’ll define zoom factors, add controls to the interface, and then add a zoom method that’s invoked by the controls.
To add the ability for users to zoom images in an image browser:
Add a zoom method.
This method responds to the zoom controls in the user interface. You’ll add the controls later. The method needs to set the zoom value and then signal the need to update the browser display.
- (IBAction) zoomSliderDidChange:(id)sender
{
[mImageBrowser setZoomValue:[sender floatValue]];
[mImageBrowser setNeedsDisplay:YES];
}
Add the method signature to the
ImageBrowserController.h
file.- (IBAction) zoomSliderDidChange:(id)sender;
Save the
ImageBrowserController.h
andImageBrowserController.m
files.Double-click the
MainMenu.nib
file to open Interface Builder.Drag a Horizontal Slider from the Library to the browser window and position it in the lower left of the window.
In the Size inspector for the slider and set the Autosizing struts appropriately.
Set the size to Mini.
In the Attributes inspector set the State to Continuous to cause the action method to send its state continuously while the mouse is down.
Set the slider minimum and maximum values.
Enter
0
for the minimum value and1.0
for the maximum value.Control-drag from the slider to the ImageBrowserController icon and in the connections panel choose
zoomSliderDidChange:
.Save the
MainMenu.nib
file.In Xcode, click Build and Go.
Try the zoom controls and make sure they work.
Supporting Removing and Reordering Items
The image browser will be far more useful if users can remove items and reorder them. You need to set the option to allow reordering. Then you need to implement the methods defined by the IKImageBrowserDataSource
protocol that support editing items.
Follow these steps to support removing and reordering:
Open the
ImageBrowserController.m
file.Implement the
IKImageBrowserDataSource
protocol method for removing items.- (void) imageBrowser:(IKImageBrowserView *) view removeItemsAtIndexes: (NSIndexSet *) indexes
{
[mImages removeObjectsAtIndexes:indexes];
}
Implement the
IKImageBrowserDataSource
protocol method to move items from one location to another.This method first removes items from the data source and stores them temporarily in an array. Then it inserts the removed items into the images array at the new location.
- (BOOL) imageBrowser:(IKImageBrowserView *) view moveItemsAtIndexes: (NSIndexSet *)indexes toIndex:(unsigned int)destinationIndex
{
int index;
NSMutableArray *temporaryArray;
temporaryArray = [[NSMutableArray alloc] init];
for(index=[indexes lastIndex]; index != NSNotFound;
index = [indexes indexLessThanIndex:index])
{
if (index < destinationIndex)
destinationIndex --;
id obj = [mImages objectAtIndex:index];
[temporaryArray addObject:obj];
[mImages removeObjectAtIndex:index];
}
// Insert at the new destination
int n = [temporaryArray count];
for(index=0; index < n; index++){
[mImages insertObject:[temporaryArray objectAtIndex:index]
atIndex:destinationIndex];
}
return YES;
}
In the
awakeFromNib
method, set the image browser to allow reordering.After modification, the method should look as follows:
- (void) awakeFromNib
{
mImages = [[NSMutableArray alloc] init];
mImportedImages = [[NSMutableArray alloc] init];
[mImageBrowser setAllowsReordering:YES];
[mImageBrowser setAnimates:YES];
}
In Xcode, click Build and Go.
Add images to the browser. Then try deleting a few items. Select several items and move them to a new location.
Supporting Drag and Drop
It is convenient for users to be able to drag items directly to the browser. Drag and drop requires that you set a dragging destination delegate and implement three methods of the NSDraggingDestination
protocol. (See Drag and Drop Programming Topics.)
To support the ability for users to drag and drop images, follow these steps:
In the
awakeFromNib
method, set the dragging destination delegate for the image browser.After modification, the method should look as follows:
- (void) awakeFromNib
{
mImages = [[NSMutableArray alloc] init];
mImportedImages = [[NSMutableArray alloc] init];
[mImageBrowser setAllowsReordering:YES];
[mImageBrowser setAnimates:YES];
[mImageBrowser setDraggingDestinationDelegate:self];
}
Implement the
performDragOperation:
method of theNSDraggingDestination
protocol.This method looks for paths in the pasteboard. If there are paths, the method retrieves them, adds them to the data source, then reloads the image browser.
- (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
{
NSData *data = nil;
NSString *errorDescription;
NSPasteboard *pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:NSFilenamesPboardType])
data = [pasteboard dataForType:NSFilenamesPboardType];
if(data){
NSArray *filenames = [NSPropertyListSerialization
propertyListFromData:data
mutabilityOption:kCFPropertyListImmutable
format:nil
errorDescription:&errorDescription];
int i;
int n = [filenames count];
for(i=0; i<n; i++){
[self addImagesWithPath:[filenames objectAtIndex:i] recursive:NO];
}
[self updateDatasource];
}
return YES;
}
Implement the
draggingEntered:
method of theNSDraggingDestination
protocol.- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
return NSDragOperationCopy;
}
Implement the
draggingUpdated:
method of theNSDraggingDestination
protocol.- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
return NSDragOperationCopy;
}
In Xcode, click Build and Go.
Drag some images to the image browser. Then add some more images. Note that images dragged to the browser are added at the end.
Setting Browser and Cell Appearance
The IKImageBrowserView
class provides several methods that control the image browser and browser item appearance. You can set the display style for cells so that the cells are shadowed, outlined, or appear with a title or subtitle. You can specify whether to constrain the size of items to their original size. You can also set the background color of the image browser and a number of other options, all of which are detailed in IKImageBrowserView Class Reference.
You set cell appearance by including the appropriate statements in the awakeFromNib:
method, such as:
[mImageBrowser setCellsStyleMask:IKCellsStyleOutlined | IKCellsStyleShadowed]; |
[mImageBrowser setConstrainsToOriginalSize:YES]; |
If you support groups, you can set bezel and disclosure styles on a per-group basis—the group style is not a property of the image browser.
Copyright © 2008 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2008-06-09