10.5. Images

The UI Kit framework provides classes to work with individual images and an image view class that can display them. Apple has also provided a special type of navigation controller for selecting images from a library.

10.5.1. The Image Object

The UIImage class encapsulates an image and its underlying data. It can draw directly inside a view or act as an image container in more powerful image view classes. The class provides methods to load an image from various sources, set the image's orientation on the screen, and provide information about the image. For simple graphics, you can use UIImage objects in a view class's drawRect method to render both images and patterns.

You can initialize a UIImage object with the contents of a file, website URL, raw data, or the contents of a Core Graphics image. Both static and instance methods exist; these can either reference and cache images or instantiate new image objects, depending on the needs of your application.

The easiest way to reference an image is through the UIImage class's static methods. Rather than managing instances of images, the static methods provide a direct interface to shared objects residing in the framework's internal memory cache. This helps declutter your application and eliminates the need to clean up. Both static and instance methods exist to create the same objects.

10.5.1.1. Working with files (static methods)

The imageNamed and imageWithContentsOfFile methods allow you to access image files by either the name of the file within your bundle or the full path to the file, respectively.

To access a file within your application's program folder, use the imageNamed method:

UIImage *image = [ UIImage imageNamed: @"image.png" ];

To access a file anywhere else within your sandbox, use the imageWithContentsOfFile method:

NSString *path = [ NSString stringWithFormat: @"%@/Documents/
image.png", NSHomeDirectory() ];
UIImage *image = [ UIImage imageWithContentsOfFile: path ];

10.5.1.2. Working with URLs and raw data (static methods)

If the image is resident in memory, you can initialize a UIImage object by creating an NSData object and providing it as raw input to the imageWithData method. You've already learned about the NSData class in previous chapters. You can initialize NSData structures in a number of ways, including the results of an HTTP fetch.

In the example to follow, the variable imagePtr is assumed to be a pointer to the raw image data in your application, and imageSize is assumed to be the image's size in memory:

NSData *imageData = [ NSData initWithBytes: imagePtr length: imageSize ];
UIImage *image = [ UIImage imageWthData: imageData ];

To create a UIImage containing the contents of a web object, use the NSData class to download it, and then initialize your image object:

NSURL *url = [ NSURL URLWithString:
    @" http://oreilly.com/catalog/covers/9781934356258_cat.gif"
];

NSData *imageData = [ NSData dataWithContentsOfUrl: url ];
UIImage *image = [ UIImage imageWithData: imageData ];

10.5.1.3. Working with Core Graphics (static methods)

If you're programming games or other graphics applications using the Core Graphics framework, you can initialize a UIImage object with the contents of an existing CGImage object:

UIImage *image = [ UIImage imageWithCGImage: myCGImageRef ];

10.5.1.4. Working with files (instance methods)

The initWithContentsOfFile methods allow you to access image files by a pathname. An example follows:

NSString *path = [ NSString stringWithFormat: @"%@/Documents/image.png",
    NSHomeDirectory()
];

UIImage *image = [ [ UIImage alloc ] initWithContentsOfFile: path ];

10.5.1.5. Working with URLs and raw data (instance methods)

If the image is resident in memory, you can initialize a UIImage object by creating an NSData object and providing it as raw input to the initWithData method.

In the example to follow, the variable imagePtr is assumed to be a pointer to the raw image data in your application, and imageSize is assumed to be the image's size in memory:

NSData *imageData = [ NSData initWithBytes: imagePtr length: imageSize ];
UIImage *image = [ [ UIImage alloc ] initWithData: imageData ];

To instantiate a UIImage containing the contents of a web object, use the NSData class to download it, and then initialize your image object:

NSURL *url = [ NSURL URLWithString:
    @" http://oreilly.com/catalog/covers/9781934356258_cat.gif"
];

NSData *imageData = [ NSData dataWithContentsOfUrl: url ];
UIImage *image = [ [ UIImage alloc ] initWithData: imageData ];

10.5.1.6. Working with Core Graphics (instance methods)

If you're programming games or other graphics applications using the Core Graphics framework, an instance method also exists to initialize a UIImage object with the contents of an existing CGImage object:

UIImage *image = [ [ UIImage alloc ] initWithCGImage: myCGImageRef ];

10.5.1.7. Displaying an image

View classes use internal drawing routines called when their drawRect methods are invoked. Unlike other image classes, a UIImage object cannot be attached directly to a view object as a subview, because it isn't a view class. Instead, a UIView class calls an image's drawInRect method from within the view's drawRect routine. This instructs the image to render within the UIView class's display region.

A view object's drawRect method is called whenever a portion of its window needs to be rendered. To render the contents of a UIImage inside the window, invoke the object's drawInRect method:

- (void)drawRect:(CGRect)rect {
    CGRect myRect;

    myRect.origin.x = 0;
    myRect.origin.y = 0;
    myRect.size = myImage.size;

    [ myImage drawInRect: myRect ];
}

Be careful not to allocate any new objects inside the drawRect method, because it's called every time the window needs to be redrawn.

The drawRect method is called only when the view is initially drawn. To force an update, use the view class's setNeedsDisplay or setNeedsDisplayInRect method:

[ myView setNeedsDisplay ];
[ myVIew setNeedsDisplayInRect: redrawThisRect ];

10.5.1.8. Drawing patterns

If the image is a pattern, you can use another method provided in the UIImage class, drawAsPatternInRect, to repeat the image throughout the entire view region:

[ myImage drawAsPatternInRect: rect ];

This method will tile the image within the frame being drawn.

10.5.1.9. Orientation

An image's orientation determines how it's rotated in the display. Because the iPhone can be held one of six different ways, it may be necessary to rotate all of your images if the orientation changes. Use the image's imageOrientation property to set its orientation:

myImage.imageOrientation = UIImageOrientationUp;

You can set the following orientations.

Orientation Description
UIImageOrientationUp Default orientation
UIImageOrientationDown Image rotated 180 degrees
UIImageOrientationLeft Image rotated 90 degrees counter-clockwise
UIImageOrientationRight Image rotates 90 degrees clockwise
UIImageOrientationUpMirrored Up, horizontally flipped
UIImageOrientationDownMirrored Down, horizontally flipped
UIImageOrientationLeftMirrored Rotated 90 degrees counter-clockwise, vertically flipped
UIImageOrientationRightMirrored Rotated 90 degrees clockwise, vertically flipped

10.5.1.10. Image size

You can read an image's size by reading the size property. This provides a CGSize structure containing width and height variables:

CGSize imageSize = myImage.size;
NSLog(@"Image size: %dx%d", imageSize.width, imageSize.height);

10.5.2. ImageFun: Fun with Images and Patterns

This example illustrates the rendering of images and patterns within a view class's drawRect method. You'll create an empty subclass of UIVIew and then override the drawRect method to include rendering routines, drawing up a pattern and an icon in the main window. It will download two image files, which it will use to draw a pattern and image on the screen (Figure 10-5). The image will fade in on a timer.

Figure 10-5. ImageFun example


You can compile this application, shown in Examples Example 10-11 through Example 10-13, with the SDK by creating a window-based application project named ImageFun. Be sure to pull out the Interface Builder code if you'd like to see how all objects are created from scratch.

Example 10-11. ImageFun application delegate prototypes (ImageFunAppDelegate.h)
#import <UIKit/UIKit.h>

@interface ImageFunView : UIView
{
    UIImage *pattern;
    UIImage *image;
    float alpha;
}
- (void)drawRect:(CGRect)rect;
@end

@interface ImageFunAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    ImageFunView *mainView;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

Example 10-12. ImageFun application delegate (ImageFunAppDelegate.m)
#import "ImageFunAppDelegate.h"

@implementation ImageFunView

- (id)initWithFrame:(CGRect) rect {
    self = [ super initWithFrame: rect ];
    if (self != nil) {
        NSLog(@"Loading pattern");
        NSURL *url = [ NSURL URLWithString:
             @"http://www.zdziarski.com/demo/1.png" ];
        pattern = [ [ UIImage alloc ] initWithData:
             [ NSData dataWithContentsOfURL: url ]
         ];

        NSLog(@"Loading image");
        NSURL *url2 = [ NSURL URLWithString:
             @"http://www.zdziarski.com/demo/2.png" ];
        image = [ [ UIImage alloc ] initWithData:
             [ NSData dataWithContentsOfURL: url2 ]
         ];

        alpha = 0.0;
    }

    return self;
}

- (void)drawRect:(CGRect)rect {
    CGRect myRect;

    myRect.size = image.size;
    myRect.origin.x = (320.0 - image.size.width) / 2;
    myRect.origin.y = (460.0 - image.size.height) / 2;

    [ pattern drawAsPatternInRect: rect ];

    [ image drawInRect: myRect blendMode: kCGBlendModeNormal alpha: alpha ];

    NSTimer *timer = [ NSTimer scheduledTimerWithTimeInterval: 0.01
        target: self
            selector: @selector(handleTimer:)
        userInfo: nil
        repeats: NO
    ];
}

- (void) handleTimer: (NSTimer *) timer {
    if (alpha < 1.0) {
        alpha += 0.01;
        [ self setNeedsDisplay ];
    }
}

- (void)dealloc {
    [ super dealloc ];
}
@end

@implementation ImageFunAppDelegate

@synthesize window;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    CGRect screenBounds = [ [ UIScreen mainScreen ] applicationFrame ];
    CGRect viewRect = screenBounds;
    viewRect.origin.x = viewRect.origin.y = 0;

    self.window = [ [ [ UIWindow alloc ] initWithFrame: screenBounds ]
        autorelease
    ];

    mainView = [ [ ImageFunView alloc ] initWithFrame: viewRect ];

    [ window addSubview: mainView ];
    [ window makeKeyAndVisible ];
}

- (void)dealloc {
    [ mainView release ];
    [ window release ];
    [ super dealloc ];
}

@end

                                          

Example 10-13. ImageFun main (main.m)
#import <UIKit/UIKit.h>

int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, @"ImageFunAppDelegate");
    [pool release];
    return retVal;
}

                                          

10.5.3. UIImageView: An Image with a View

The UIImageView class provides a way to encapsulate an image within a view class or to provide an animation within a view. This is useful when an image needs to be anchored to other view objects, or for applications such as slideshows, where an entire view region might contain an image.

A UIImageView object acts as a view class wrapper for UIImage; that is, you first create the UIImage object, and then you use it to initialize a UIImageView object using the UIImageView class's initWithImage or setImage methods:

UIImage *image = [ UIImage imageNamed: @"image.png" ];
UIImageView *imageView = [ [ UIImageView alloc ]
    initWithImage: image
];

You can assign an array of images to an image view in order to build an animation:

NSMutableArray *myImages = [ [ NSMutableArray alloc ] init ];
[ myImages addItem: myImage1 ];
[ myImages addItem: myImage2 ];
[ myImages addItem: myImage3 ];
imageView.animationImages = myImages;

Once you have defined an animation, set its duration and repeat count properties:

imageView.animationDuration = 5.0; /* Five seconds */
imageView.repeatCount = 3;

Once the new UIImageView object has been configured, you can attach it to any type of view object, table cell, or other similar object:

[ myOtherView addSubview: imageView ];

To scale the image, create a new frame reflecting the adjusted size. You can then apply the new image size by assigning the frame to the object's frame property:

CGRect rect = imageView.frame;
CGSize size = CGSizeMake(160.0, 240.0);
rect.size = size;
imageView.frame = rect;

To enable or disable the view's animation, use the startAnimating and stopAnimating methods:

[ imageView startAnimating ];
if ([ imageView isAnimating ] == YES) {
    [ imageView stopAnimating ];
}

You'll use the UIImageView class extensively in Chapters Chapter 12 and Chapter 13.

10.5.4. Image Pickers

An image picker is a type of navigation controller class that allows you to add a simple image selector or camera interface to your application. The user is presented with an image selection screen and can choose a photo from his photo library, saved photo album, or the camera. When the user selects a photo, the picker's delegate is notified using methods from the UIImagePickerDelegate protocol.

You create the image picker as a UIImagePickerController object, and you can add it to the window as a standalone navigation controller:

UIImagePickerController *picker = [ [ UIImagePickerController alloc ] init ];
[ window addSubview: [ picker view ] ];

                                          

10.5.4.1. Image sources

You can define various sources to present to the user using the sourceType property:

picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;

You may use the following sources.

Style Description
UIImagePickerControllerSourceTypePhotoLibrary Photo library
UIImagePickerControllerSourceTypeCamera Camera
UIImagePickerControllerSourceTypeSavedPhotosAlbum Saved photos

10.5.4.2. Image editing

To allow the user to move and scale the image to his liking, enable image editing by setting the allowsImageEditing property to YES:

picker.allowsImageEditing = YES;

10.5.4.3. Image selection

When the user has selected a picture, the picker's delegate is notified through its didFinishPickingImage method. The delegate is supplied with a UIImage object containing the image and an NSDictionary containing any editing properties, if editing was enabled.

To assign a delegate to the picker, set the picker's delegate property:

picker.delegate = self;

Add the following method to your delegate class to be notified when the user picks an image:

- (void)imagePickerController:(UIImagePickerController *)picker
    didFinishPickingImage:(UIImage *)image
    editingInfo:(NSDictionary *)editingInfo
{
    /* Code to handle image selection */
}

The parameters provide you with a pointer to the image picker controller reporting the action, so you can handle multiple pickers in one delegate. You're also provided with a pointer to the UIImage object itself and a dictionary object containing information about how the image was scaled and moved on the screen.

You'll also want to be notified if the user cancels image selection. Add a method named imagePickerControllerDidCancel to your delegate. It will be invoked with a pointer to the image picker that was canceled:

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    /* Additional code to handle image selection canceled */
}