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

Ray Wenderlich
Create a full-featured photo viewer easily!

Create a full-featured photo viewer easily!

It’s a common requirement for apps to display images in a similar manner to the Photos app on the iPhone. One simple way to implement this is by using the TTPhotoViewController from the open source Three20 library.

In this Three20 tutorial, we will cover how you can use Three20 to make a simple app that displays a set of photos just like the Photos app does – complete with flicking between photos, pinching to zoom, captions, and full screen mode.

Best of all, because you’re using Three20, you get a lot of other great features built in such a built-in thumbnail viewer, being able to retrieve higher resolution images over the network, and caching the retrieved images locally!

If you haven’t used Three20 before, you may wish to check out the Introduction to Three20 tutorial first. However, this Three20 tutorial is fairly self-contained, so it isn’t required!

Adding Three20 To Your Project

Create a new project in XCode by going to File\New Project, select Window-based Application, and make sure to choose iPhone for the product. Click Choose, and name the Project “PhotoViewer.”

Next you need to get a copy of the three20 library. The best way to get the latest copy of three20 is to use the source control system “git.” If you do not already have this installed, you can download a Mac installer.

Once you have git installed, follow the excellent instructions on the three20 web page for instructions on how to pull down the latest code and add it to your project.

At the time of writing this post, there’s one additional step you need to take. The three20 projects are currently set to use iOS 3.0 as the Base SDK, but assuming you are using XCode 3.2.3, this SDK is no longer available. Therefore, you need to double click on each of the Three20 XCode projects in your Groups & Files list to open up the project, go to Project\Edit Project Settings in each Three20 project, and set the Base SDK to iPhone Device 4.0.

Once you’ve done that, add the following to the top of PhotoViewerAppDelegate.h: #import “Three20/Three20.h”

Then compile your project – if it works three20 has been successfully integrated and you can move onto the next step!

Creating a TTPhoto Class

To display a photo in the TTPhotoViewController, Three20 needs some information from our app. Specifically, it needs to know the size of the image, a URL for where to find the image, an optional caption for the image, and few other pieces of information.

To give Three20 this information, we need to create a class representing our photos that implements the TTPhoto protocol. The definition for the TTPhoto protocol looks something like this:

@protocol TTPhoto <NSObject, TTURLObject>
@property (nonatomic) CGSize size;
@property (nonatomic, copy) NSString* caption;
- (NSString*)URLForVersion:(TTPhotoVersion)version;
@property (nonatomic, assign) id<TTPhotoSource> photoSource;
@property (nonatomic) NSInteger index;

So we need to make sure our class has properties for the size of the image and any caption we want to display. We need to create a method to return a URL for the image. And we’ll need properties for a photoSource and index, but we’ll talk about what those mean later.

A note about the method to retrieve a URL – note it takes a TTPhotoVersion parameter. This has values such as TTPhotoVersionSmall or TTPhotoVersionLarge, so we can return different URLs for images of different levels of quality. In fact that’s what we’ll do in this Three20 tutorial – we’ll include a couple small but low quality images embedded with the app, and a large but high quality image on a web server.

So let’s create our own class that implements this protocol. 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 Photo.m (and make sure also create Photo.h is selected), and click Finish.

Replace Photo.h with the following:

#import <Foundation/Foundation.h>
#import <Three20/Three20.h>
@interface Photo : NSObject <TTPhoto> {
    NSString *_caption;
    NSString *_urlLarge;
    NSString *_urlSmall;
    NSString *_urlThumb;
    id <TTPhotoSource> _photoSource;
    CGSize _size;
    NSInteger _index;
@property (nonatomic, copy) NSString *caption;
@property (nonatomic, copy) NSString *urlLarge;
@property (nonatomic, copy) NSString *urlSmall;
@property (nonatomic, copy) NSString *urlThumb;
@property (nonatomic, assign) id <TTPhotoSource> photoSource;
@property (nonatomic) CGSize size;
@property (nonatomic) NSInteger index;
- (id)initWithCaption:(NSString *)caption urlLarge:(NSString *)urlLarge urlSmall:(NSString *)urlSmall urlThumb:(NSString *)urlThumb size:(CGSize)size;

So basically we’re just creating a subclass of NSObject, but we implement the TTPhoto protocol. We create some properties for caption, photoSource, size, and index, since they’re required in the TTPhoto protocol, and some instance variables to back them up. We also create some instance variables to hold URLs to the three versions of the images we’re going to use for this Three20 tutorial – a “large” version, a “small” version, and a thumbnail version.

Now move over to Photo.m and replace it with the following:

#import "Photo.h"
@implementation Photo
@synthesize caption = _caption;
@synthesize urlLarge = _urlLarge;
@synthesize urlSmall = _urlSmall;
@synthesize urlThumb = _urlThumb;
@synthesize photoSource = _photoSource;
@synthesize size = _size;
@synthesize index = _index;
- (id)initWithCaption:(NSString *)caption urlLarge:(NSString *)urlLarge urlSmall:(NSString *)urlSmall urlThumb:(NSString *)urlThumb size:(CGSize)size {
    if ((self = [super init])) {
        self.caption = caption;
        self.urlLarge = urlLarge;
        self.urlSmall = urlSmall;
        self.urlThumb = urlThumb;
        self.size = size;
        self.index = NSIntegerMax;
        self.photoSource = nil;
    return self;
- (void) dealloc {
    self.caption = nil;
    self.urlLarge = nil;
    self.urlSmall = nil;
    self.urlThumb = nil;    
    [super dealloc];
#pragma mark TTPhoto
- (NSString*)URLForVersion:(TTPhotoVersion)version {
    switch (version) {
        case TTPhotoVersionLarge:
            return _urlLarge;
        case TTPhotoVersionMedium:
            return _urlLarge;
        case TTPhotoVersionSmall:
            return _urlSmall;
        case TTPhotoVersionThumbnail:
            return _urlThumb;
            return nil;

We create a constructor for our Photo that we’ll use later, initializing everything to what’s passed in. Note we just initialize the index and photoSource to dummy values for now – they will be updated later after the constructor is called.

The only method of real interest in this class is URLForVersion: here we examine the version passed in, and return the appropriate URL based on the version. Note we are not including a “medium quality” image in this Three20 tutorial, so we just return the URL for the large image in this case.

By implementing this method, we’ll see that Three20 is smart enough to display the small image first (since it should load more quickly) and then follow up with retrieving the high quality image in the background and replacing the small image when its ready.

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;

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;

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; = 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; = 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;

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 75×75, 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" 
                                                          size:CGSizeMake(1024, 768)] autorelease];
            Photo *instantPoetry = [[[Photo alloc] initWithCaption:@"Instant Poetry" 
                                                              size:CGSizeMake(1024, 748)] autorelease];
            Photo *rpgCalc = [[[Photo alloc] initWithCaption:@"RPG Calc" 
                                                        size:CGSizeMake(640, 920)] autorelease];
            Photo *levelMeUp = [[[Photo alloc] initWithCaption:@"Level Me Up" 
                                                          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;

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];

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!

Displaying the TTPhotoViewController

The last step is to replace PhotoViewerAppDelegate.m with the following:

#import "PhotoViewerAppDelegate.h"
#import "PhotoViewController.h"
@implementation PhotoViewerAppDelegate
@synthesize window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    [[TTURLRequestQueue mainQueue] setMaxContentLength:0];
    TTNavigator *navigator = [TTNavigator navigator];
    navigator.window = window;
    TTURLMap *map = navigator.URLMap;
    [map from:@"tt://appPhotos" toSharedViewController:[PhotoViewController class]];
    [navigator openURLAction:[TTURLAction actionWithURLPath:@"tt://appPhotos"]];
    [window makeKeyAndVisible];
    return YES;
- (void)dealloc {
    [window release];
    [super dealloc];

The real meat is in application:didFinishLaunchingWithOptions. The first thing we do here is set the maximum content length of the TTURLRequestQueue to 0, which means unlimited. If we forget to do this, Three20 will retrieve an error if we try to retrieve a file that is over a set limit. This is intended to warn you in case you didn’t mean to download something that big, but we don’t want those restrictions for the sake of this tutorial.

Next we start up a TTNavigator and a mapping from the tt://appPhotos URL to our PhotoViewController, and then navigate to that URL. If you’re unfamiliar how this works, you may wish to review the Introduction to Three20 tutorial.

And that’s it! Compile and run the sucker, and you should be able to navigate through your set of images with style! You’ll notice that the app shows the small images that ship with the app at first, then downloads the high res images in the background for you. Pretty cool, eh?

Photo Viewer Screenshot 1
Photo Viewer Screenshot 2
Photo Viewer Screenshot 3
Photo Viewer Screenshot 4

Where To Go From Here?

Here is a sample project with all of the code we’ve developed in the above Three20 tutorial.

The TTPhotoViewer is just one of the many great pieces of functionality available in Three20. A great way to get an overview of what’s available is to go through the TTCatalog project that comes with Three20.

Additionally, if you want to make a photo viewer but don’t want to use Three20, you could always write your own. The UIScrollView class actually contains much of the functionality to make this possible – see WWDC talk #104 for some great info on this.

If you have any comments or questions just drop a line below – and let me know how you’re using or planning to use Three20 in your apps!

Ray Wenderlich

Ray is part of a great team - the team, a group of over 100 developers and editors from across the world. He and the rest of the team are passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments


[ 1 , 2 , 3 , 4 , 5 ]
  • How could this be implemented if the photos are coming through a json response
  • Hi Ray,

    I am not able to get this working on network connection!

    Following works for me when I have images locally stored:

    NSString *imageName = [tempDict objectForKey:@"CommentImageName"];

    //Create thumbnails
    [ImageUtils checkAndSaveThumbnail:imageName andSize:CGSizeMake(100.0, 100.0) andLocation:NO andPrefixof:@"small"];

    NSString *fullImageName = [NSString stringWithFormat:@"documents://%@",imageName];
    NSString *imageSmallName = [NSString stringWithFormat:@"documents://small-%@",imageName];
    NSString *caption = [tempDict objectForKey:@"Comment"];

    // replace contents of file with some good function
    UIImage *tempImage = [UIImage imageWithContentsOfFile:[appDelegate filePath:imageName]];

    MockPhoto *tempPicture = [[MockPhoto alloc] initWithURL:fullImageName smallURL:imageSmallName size:tempImage.size caption:caption photoname:imageName];

    [photoArray addObject:tempPicture];

    ..But when I give download location like following it doesn't work! Doesn't show me thumbnails and neither large pics!

    NSString *str = [NSString stringWithFormat:@"%@/app/%d/phone/downloadImage?appId=%d&facebookId=%@", [appDelegate getDataUrl], appDelegate.currentAppID,appDelegate.currentAppID, [tempDict objectForKey:@"CommentImageId"]];

    MockPhoto *tempPicture = [[MockPhoto alloc] initWithURL:str smallURL:str size:tempImage.size caption:caption photoname:imageName];

    [photoArray addObject:tempPicture];

    I have been using same same URL with AsyncImage and it downloads image and show in ImageView fine!

    NSURL *url = [NSURL URLWithString:[delegate downloadImageUrl]];
    UIImageView *profileimage = [[UIImageView alloc] init];
    AsyncImageView* asyncImage = [[[AsyncImageView alloc] initWithFrame:CGRectMake(0, 10, 60, 60)] autorelease];
    asyncImage.imageName = [[users objectAtIndex:indexPath.row] objectForKey:@"Key"];
    [asyncImage loadImageFromURL:url];
    [profileimage addSubview:asyncImage];

    Could you please let me know what's wrong am I doing with MockPhoto?

    Raj Cool
  • I am trying to use this tutorial inside of iOS5 with ARC. It seems to almost be working, but it won't display my photos in the TTPhotoViewController class. I have a regular UITableViewController with a list of different photo albums that I would like to display. I think I set up the the segue correctly. When I tap on one of the photo albums (in the UITableViewController), it takes me to a blank TTPhotoViewController (it has two photo boxes and says 'This photo set contains no photos"). I set up the tableView:didSelectRowAtIndexPath: and pushed my TTPhotoViewController onto the stack, which seems to make it work, but my photos do not appear. When I debug this, I tested it to make sure the TTPhotos and TTPhotoSet were not nil. In both cases, they are not, yet when I push this view controller, it doesn't show my photos. When setting up my Photos, I used @"bundle://myImage.jpg".
    If anyone has any suggestions to make this work, I would greatly appreciate it. If more info is need, let me know.
  • I want to be able to show facebook comment on each photo like in facebook application. How to do this?
  • Thanks for the tutorial, everything worked fine for me except:
    I'm using a TT navigator with a tab bar, each tab bar is associated with one photo viewer(same controller with different photo source) like the one mentioned in this tutorial, the problem is that when the photo viewer is loaded, it replaces the navigation bar and tool bar of my App with its own ones.
    So my question is, can I keep at least my customized tool bar(or tab bar)? I'm not interested in the toolbar that Three20 provides, which are just previous and next buttons. I don't need the thumb view either so I don't need the navigation bar, I can switch views using my tab bar, which is not visible now...
  • I have subclassed TTphotoviewcontroller and added a button for saving the images to the camera roll. All the other buttons on the toolbar are working fine (previous, next and play) except for the one I've created by overiding in the subclassed file. I am getting the dreaded 'unrecognized selector sent to instance' error when I click the button.

    My subclassed ttphotoviewcontroller is inside a navigation controller which in turn is inside a navigation controller of a tabbarcontroller. The top nav controller has a view controller with an in-app purchase button. After purchase the user goes through to the wallpaper section containing the subclassed ttphotoviewcontroller inside a navigation controller (hope that makes sense). I have checked all the linker settings. Thanks in advance for any help.

    navigation controller
    in-app purchase view controller
    navigation controller
    subclassed photoviewcontroller
    clickActionItem selector method for button

    I create the button like so:
    _clickActionItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:
    target:self action:@selector(clickActionItem)];

    and fire with this:
    - (void) clickActionItem: (id)sender {
    NSURL *aUrl = [NSURL URLWithString:[_centerPhoto URLForVersion:TTPhotoVersionLarge]];
    NSData *data = [NSData dataWithContentsOfURL:aUrl];
    UIImage *img = [[UIImage alloc] initWithData:data];

    NSLog(@"photo:class %@", [img class]);

    UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil);
  • I need small help. I am working on small app. Which fetch photo from device and store selected photo it in app. I am using three20 framework for that. First problem i am facing that in iPad I am not able to show photo proper way. Its cut pic from right side. I also want to add Delete option so use can delete from gallery... Thanks in advance...
  • Hi ray,
    It was amazing to read your tutorials.
    I want to know the difference between
    Three20 (
    EGOPhotoViewer (

    I means both samples are developed by same developer (

    Is there any advantage of using EGOPhotoViewer over Three20?
    Is it worth to stick to Three20 for any important feature of cache, thread, etc.
    Please reply if anybody know the difference.
  • Hi Ray,

    Iam trying this demo with Xcode 4.5 , iOS 6, But iam getting error <Three20/Three20.h> not found.

    I need to implement a photo gallery, where images are dynamically fetched from server. Is their is any other library which can do this in a memory efficient way

    Ankur Chhabra
  • i've been downloaded your photoviewer project from your last post : ... but when i run it, it shows an error, it says
    semantic issue on TTPickerTextField.m
    in line 418 of TTPickerTextField.m
    [self.delegate performSelector:sel withObject:self withObject:(id)_cellViews.count-1];
    "aritmethic on pointer to interface 'id'. which is not a constant size in non fragile ABI"
  • Thank you ! this is an awesome tutorial works to the tee.. I would like to implement one additional item and that is using the location point callout and drive to it. Any input on how to add a drive to this point into the tutorial?


  • Thank you ! this is an awesome tutorial works to the tee.. I would like to implement one additional item and that is using the location point callout and drive to it. Any input on how to add a drive to this point into the tutorial?


  • Hi Matt,

    I used this few time to learn this subject. It is very interesting. I have just one questiopn.

    Can I know how to use this with "StoryBoard"?
    Is it possible? and if yes, what should I change to get it to work?

    When I run it with StoryBoard, I get the below error.

    Undefined symbols for architecture i386:
    "_OBJC_CLASS_$_TFHpple", referenced from:
    objc-class-ref in MasterViewController.o
    ld: symbol(s) not found for architecture i386
    clang: error: linker command failed with exit code 1 (use -v to see invocation)

    Thanks for your reply.

[ 1 , 2 , 3 , 4 , 5 ]

Other Items of Interest Weekly

Sign up to receive the latest tutorials from each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Unity Starter Kit!

We are considering writing a new starter kit on making a game in C# with Unity.

Would this be something you'd be interested in, and if so which option would you prefer?

    Loading ... Loading ...

Our Books

Our Team

Video Team

... 12 total!

Swift Team

... 10 total!

iOS Team

... 52 total!

Android Team

... 9 total!

OS X Team

... 11 total!

Sprite Kit Team

... 10 total!

Unity Team

... 9 total!

Articles Team

... 10 total!