Three20 Tutorial for iOS: How To Use the Three20 Photo Viewer

A Three20 tutorial that shows you how to use the open source Three20 library’s photo viewer inside your apps. By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Creating a Photo Source

Our next step is to create an object that the TTPhotoViewController can use to figure out what photos it should display. In Three20, we do that by creating an object that implements the TTPhotoSource protocol. The definition for the TTPhotoSource protocol looks something like this:

@protocol TTPhotoSource <TTModel, TTURLObject>
@property (nonatomic, copy) NSString* title;
@property (nonatomic, readonly) NSInteger numberOfPhotos;
@property (nonatomic, readonly) NSInteger maxPhotoIndex;
- (id<TTPhoto>)photoAtIndex:(NSInteger)index;
@end

So we need to return a title for the set of photos (used in the thumbnail view of the Photo View Controller), the number of photos to display (and the max index), and finally a method to return a TTPhoto at a particular index.

TTPhotoSource is built so that you can retrieve the information on which photos to display over the network as well. However, for the purposes of this Three20 tutorial we’re take a simple route and just hard-code which images to display straight into our app – therefore we can bypass the remote downloading capability.

So let’s create an object that implements TTPhotoSource. Go to File\New File, choose Cocoa Touch Class\Objective-C class, make sure Subclass of NSObject is selected, and click Next. Name the new file PhotoSet.m (and make sure also create PhotoSet.h is selected), and click Finish.

Then replace PhotoSet.h with the following:

#import <Foundation/Foundation.h>
#import "Three20/Three20.h"

@interface PhotoSet : TTURLRequestModel <TTPhotoSource> {
    NSString *_title;
    NSArray *_photos;
}

@property (nonatomic, copy) NSString *title;
@property (nonatomic, retain) NSArray *photos;

+ (PhotoSet *)samplePhotoSet;

@end

Nothing too special here – we declare two properties / instance variables to store the title of our photo set and contain the list of photos. We also have a static method to create a sample photo set that we’ll use later on in the app.

Note that the class derives from TTURLRequestModel. This is because the TTPhotoSource protocol requires us to implement the TTModel and TTURLObject protocols as well, and deriving from this class makes taking care of that a lot easier.

Next go to PhotoSet.m and replace it with the following:

#import "PhotoSet.h"
#import "Photo.h"

@implementation PhotoSet
@synthesize title = _title;
@synthesize photos = _photos;

- (id) initWithTitle:(NSString *)title photos:(NSArray *)photos {
    if ((self = [super init])) {
        self.title = title;
        self.photos = photos;
        for(int i = 0; i < _photos.count; ++i) {
            Photo *photo = [_photos objectAtIndex:i];
            photo.photoSource = self;
            photo.index = i;
        }        
    }
    return self;
}

- (void) dealloc {
    self.title = nil;
    self.photos = nil;    
    [super dealloc];
}

#pragma mark TTModel

- (BOOL)isLoading { 
    return FALSE;
}

- (BOOL)isLoaded {
    return TRUE;
}

#pragma mark TTPhotoSource

- (NSInteger)numberOfPhotos {
    return _photos.count;
}

- (NSInteger)maxPhotoIndex {
    return _photos.count-1;
}

- (id<TTPhoto>)photoAtIndex:(NSInteger)photoIndex {
    if (photoIndex < _photos.count) {
        return [_photos objectAtIndex:photoIndex];
    } else {
        return nil;
    }
}
@end

First we create a constructor that takes a title and a list of photos. We squirrel those off, and then we go through each photo object and set its photo source to ourselves, and the index to the position of the photo in our list. If you recall, these are the two properties in TTPhoto that we said we'd mention later.

Next we have to implement a couple methods in TTModel that would be handy if we were retrieving the list of photos from a remote source. Since we're hardcoding them in, we just return that we've already loaded them and go on our way!

Finally we have the most important methods: the required TTPhotoSource methods. However these are quite simple to implement - we just return the appropriate photo from the array.

Adding Some Photos

The next thing we need to do is actually add some photos to work with into the project! You can use some sample photos that I have gathered for this tutorial, or you can use your own. If you create your own, for each photo make a thumbnail versions 75x75, a small versions the size of the iPhone screen (in portrait or landscape), and a large version as large as you'd like (and upload the large version to a web server somewhere).

Once you've downloaded or created the images, drag them to the Resources folder in your project, make sure "Copy items into destination group's folder (if needed)" is checked, and click add. Then add the following code to the bottom of PhotoSet.m (you'll need to tweak this if you created your own images):

static PhotoSet *samplePhotoSet = nil;

+ (PhotoSet *) samplePhotoSet {
    @synchronized(self) {
        if (samplePhotoSet == nil) {
            Photo *mathNinja = [[[Photo alloc] initWithCaption:@"Math Ninja" 
                                                      urlLarge:@"http://www.raywenderlich.com/downloads/math_ninja_large.png" 
                                                      urlSmall:@"bundle://math_ninja_small.png" 
                                                      urlThumb:@"bundle://math_ninja_thumb.png" 
                                                          size:CGSizeMake(1024, 768)] autorelease];
            Photo *instantPoetry = [[[Photo alloc] initWithCaption:@"Instant Poetry" 
                                                          urlLarge:@"http://www.raywenderlich.com/downloads/instant_poetry_large.png" 
                                                          urlSmall:@"bundle://instant_poetry_small.png" 
                                                          urlThumb:@"bundle://instant_poetry_thumb.png" 
                                                              size:CGSizeMake(1024, 748)] autorelease];
            Photo *rpgCalc = [[[Photo alloc] initWithCaption:@"RPG Calc" 
                                                    urlLarge:@"http://www.raywenderlich.com/downloads/rpg_calc_large.png" 
                                                    urlSmall:@"bundle://rpg_calc_small.png" 
                                                    urlThumb:@"bundle://rpg_calc_thumb.png" 
                                                        size:CGSizeMake(640, 920)] autorelease];
            Photo *levelMeUp = [[[Photo alloc] initWithCaption:@"Level Me Up" 
                                                      urlLarge:@"http://www.raywenderlich.com/downloads/level_me_up_large.png" 
                                                      urlSmall:@"bundle://level_me_up_small.png" 
                                                      urlThumb:@"bundle://level_me_up_thumb.png" 
                                                          size:CGSizeMake(1024, 768)] autorelease];
            NSArray *photos = [NSArray arrayWithObjects:mathNinja, instantPoetry, rpgCalc, levelMeUp, nil];
            samplePhotoSet = [[self alloc] initWithTitle:@"My Apps" photos:photos];
        }
    }
    return samplePhotoSet;
}

This is just a helper method to create a photo set with four hard-coded images, using the constructors we created for the classes.

Creating the TTPhotoViewController

Believe it or not, that was the hardest part - everything from here on out is cake.

Let's start by creating a TTPhotoViewController. Go to File\New File, choose Cocoa Touch Class\Objective-C class, make sure Subclass of NSObject is selected, and click Next. Name the new file PhotoViewController.m (and make sure also create PhotoViewController.h is selected), and click Finish.

Replace PhotoViewController.h with the following:

#import <Foundation/Foundation.h>
#import <Three20/Three20.h>

@class PhotoSet;

@interface PhotoViewController : TTPhotoViewController {
    PhotoSet *_photoSet;
}

@property (nonatomic, retain) PhotoSet *photoSet;

@end

Here we simply derive from TTPhotoViewController and keep track of the PhotoSet we wish to display.

Then replace TTPhotoViewController.m with the following:

#import "PhotoViewController.h"
#import "PhotoSet.h"

@implementation PhotoViewController
@synthesize photoSet = _photoSet;

- (void) viewDidLoad {
    self.photoSource = [PhotoSet samplePhotoSet];
}

- (void) dealloc {
    self.photoSet = nil;    
    [super dealloc];
}

@end

All we have to do here is set the photoSource property (part of TTPhotoViewController) to our class that implements TTPhotoSource in viewDidLoad. That's it - yowza!

So let's get this view controller displayed so we can admire all our cool photos!