Making a Video (and Photo) Thumbnail Gallery using Three20 Framework

There is a huge number of tutorials on how to use three20 to create photo gallery but only very few on how to create photo gallery with thumbnails (they usually focus on creating galleries that start with scrollable photos.) And none at all on creating a video gallery. Therefore, I present this tutorial, which explains the method of creating a video gallery in detail. Starting from compiling three20 into a project, going through making a thumbnail gallery of photos and finally making a thumbnail gallery containing videos.

This tutorial has two parts:

1. Creating the photo gallery.

2. Converting this photo gallery to a video Gallery.

So, lets begin…

1. Creating Photo Gallery

Step 1: Create an Xcode Project

If you do not already have a project in which you want to create the gallery, now would be the time to create one. If you are starting from scratch, create a ‘Window-based’ project in Xcode. Lets name it “PhotoVideoGallery”, which is the name that will be used from now on in the tutorial.

Step 2: Download and build the three20 framework.

First of all, download the latest version of three20 from github. Now that we have three20, we need to add it to our project.

This little tutorial by the creator of three20 is quite convenient for building it. Here are the main steps so you don’t have to navigate away:

1. Make a new folder on the desktop and copy both three20 and your project (a copy preferably) into it.

2. Open terminal and move to the desktop using this command

cd ~/Desktop

3. Three20 includes a python script that adds it to Xcode iOS projects. We can run the script by running this command in the terminal:

python three20/src/scripts/ttmodule.py -p PhotoVideoGallery/PhotoVideoGallery.xcodeproj Three20

4. What? there’s no step 4🙂 Simple as that!!!

Note: If you do not want to copy/move your project, you can build three20 into it using this terminal command replacing your specific paths and project’s name:

python path/to/three20/src/scripts/ttmodule.py -p path/to/myProjectName.xcodeproj Three20

You might get an alert when you open your project that your project has been altered from an outside entity. In that case, you need to click on the “Read from Disk” button.

Step 3: Start making the gallery, finally

First a word on how this works. We need a thumbnail gallery, which needs a source that provides it the photos to display. This source needs photos so it can give them to the gallery. So we have to make three new classes, one for the photo, one for the photo source and one for the gallery.

The Photo Class

Create a subclass called “Photo” of NSObject and make it conform to the protocol <TTPhoto>. Now according to this protocol, our photo must have the following attributes and methods:

* The photo source that the photo belongs to.
 @property (nonatomic, assign) id photoSource;

 * The size of the photo.
 @property (nonatomic) CGSize size;

 * The index of the photo within its photo source.
 @property (nonatomic) NSInteger index;

 * The caption of the photo.
 @property (nonatomic, copy) NSString* caption;

 * Gets the URL of one of the differently sized versions of the photo.
 - (NSString*)URLForVersion:(TTPhotoVersion)version;

we are going to use an -init method to set these attributes. So our Photo.h looks like this

 
@interface Photo : NSObject  {
      NSString *_caption;
      NSString *_urlLarge;
      NSString *_urlSmall;
      NSString *_urlThumb;
      id  _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  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;
@end

and Photo.m looks like this:

#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;
       default:
         return nil;
     }
}

@end

The Photo Source Class

Create a subclass called “PhotoSource” of TTURLRequestModel (so that we can also download photos from a server using the same class) and make it conform to the protocol <TTPhotoSource>. Now according to this protocol, our photo must have the following attributes and methods:


* The title of this collection of photos.
 @property (nonatomic, copy) NSString* title;

* The total number of photos in the source, independent of the number that have been loaded.
 @property (nonatomic, readonly) NSInteger numberOfPhotos;

* The maximum index of photos that have already been loaded.
 @property (nonatomic, readonly) NSInteger maxPhotoIndex;

- (id)photoAtIndex:(NSInteger)index;

So our PhotoSource.h looks like:

@interface PhotoSource : TTURLRequestModel  {
   NSString *_title;
   NSArray *_photos;
}

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

@end

and our PhotoSource.m should look like:

#import "PhotoSource.h"
#import "Photo.h"

@implementation PhotoSource
@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)photoAtIndex:(NSInteger)photoIndex {
     if (photoIndex < _photos.count) {
         return [_photos objectAtIndex:photoIndex];
     } else {
         return nil;
     }
}

static PhotoSource *samplePhotoSet = nil;

+ (PhotoSet*) samplePhotoSet {
     @synchronized(self) {
       if (samplePhotoSet == nil) {
         Photo *firstPhoto = [[[Photo alloc] initWithCaption:@"Lake" 
                                                     urlLarge:@"bundle://Lake.jpg" 
                                                     urlSmall:nil 
                                                     urlThumb:@"bundle://Lake_Thumb.png" 
                                                     size:CGSizeMake(1024, 768)] autorelease];

         Photo *secondPhoto = [[[Photo alloc] initWithCaption:@"Shed" 
                                                     urlLarge:@"bundle://Shed.jpg" 
                                                     urlSmall:nil 
                                                     urlThumb:@"bundle://Shed_Thumb.png"
                                                     size:CGSizeMake(768, 1024)] autorelease];

         Photo *thirdPhoto = [[[Photo alloc] initWithCaption:@"Tree" 
                                                     urlLarge:@"bundle://Tree.jpg"
                                                     urlSmall:nil 
                                                     urlThumb:@"bundle://Tree_Thumb.png" 
                                                     size:CGSizeMake(683, 1024)] autorelease];

         NSArray *photos = [NSArray arrayWithObjects:firstPhoto, secondPhoto, thirdPhoto, nil];
         samplePhotoSet = [[self alloc] initWithTitle:@"Pictures" photos:photos];
       }
     }
     return samplePhotoSet;
 }
@end

Note: Replace the image names, paths, and sizes for this to work.

To display a thumbnail gallery we need to subclass ‘TTThumbsViewController‘ class of three20. So,

1. Follow the old creating new file routine, File > New > New File. Select the ‘UIViewController subclass’ when it asks the template for your new file. In the next step, type “TTThumbsViewController” in the ‘subclass’ text field. Give your class a name, we’ll name it “ThumbsViewController”

2. Now, ThumbsViewController needs a photoSource so add it as an attribute to our class. ThumbsViewController.h looks like:

#import 
@class PhotoSource;

@interface ThumbsViewController : TTThumbsViewController {
      PhotoSource *_myPhotoSource;
}
@property (nonatomic, retain) PhotoSource *myPhotoSource;
@end

and in the implementation we set the photo source to ours and ThumbsViewController.m looks like:


#import "ThumbsViewController.h"
#import "PhotoSource.h"

@implementation ThumbsViewController
@synthesize myPhotoSource;

- (void) viewDidLoad {
     self.myPhotoSource = [PhotoSource samplePhotoSet];
 }

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

@end

All is done, except we should display ThumbsViewController somewhere in order to be able to view our gallery. So, we add this to our app delegate :

UINavigationController *navigationController = [[UINavigationController alloc] 
                             initWithRootViewController:[[ThumbsViewController alloc] init]];
[self.window navigationController];
[navigationController release];


Here is what it should look like and when a thumbnail is tapped, the photos are opened in a scroller, just like int the ‘Photos’ app:

2. Converting it to a Video Gallery

Now, we shall convert this photo gallery we just made into a video gallery. So what we have to do is, in TTThumbsViewController, find the method that is called when a thumbnail is tapped and override it in our ThumbsViewController (our TTThumbsViewController subclass). The function that we’re looking for is thumbsTableViewCell: didSelectPhoto:. So Add this code to your ThumbsViewController:

- (void)thumbsTableViewCell:(TTThumbsTableViewCell*)cell didSelectPhoto:(id)photo {
        NSString *videoName = [photo caption];
        NSString *videoPath = [[NSBundle mainBundle] pathForResource:videoName ofType:@"m4v"];
        VideoPlayerViewController *videoController = [[VideoPlayerViewController alloc] 
                               initWithPath:videoPath];
        [self presentModalViewController:videoController animated:YES];
        [videoController readyPlayer];
}


Note that photo’s caption that we had previously set in initWithCaption: urlLarge: urlSmall urlThumb size: inside the PhotoSource class is being used as the name of the video in this case. The ivar caption is displayed in the photo scroller. Since we are not loading the photo scroller in this case, we can use it to store the names of our videos so that we can later use them to get required video from the app bundle. Also note that VideoPlayerViewController is another UIViewController subclass that manages the MPMoviePlayer. The player can also be initialized and loaded here directly.

Legend: Bolded items are keywords specific to Three20 framework so they can be distinguished from our subclasses

Code Courtesy for Photo and PhotoSource code: Ray Wenderlich
Code Courtesy for ThumbnailViewController code: https://github.com/crigor/three20_examples


17 Comments on “Making a Video (and Photo) Thumbnail Gallery using Three20 Framework”

  1. Rodney says:

    Hello,

    I can get the example to work as is, (as well as examples from Ray’s website).

    But, how to get this to work using iOS5 and storyboard? I cannot seem to get the view to display. My storyboard has a tableviewcontroller of items, when click a cell, it opens the details. On the details is a button that will open a photo source of thumbnails.

    I tried to open the photo source as defined in the example but I just get an empty black screen. using iOS built-in photo viewer works fine, but I want to define my own photo sources, not merely access photos in photo app.

    As you know, storyboads do not use the app delegate.m nor do they set .window, I tried loading from “prepare for segue” as well as in LoadView and in LoadDidView. same black empty screen….

    Anyway, just curious how to make the example you have work from a storyboard, where u click a button to segue to a view that displays the thumbs….

  2. rain says:

    I know about displaying a single photo by clicking on one from the thumbnail viewer is provided to you for free by Three20. In addition, the library also provides all of the native functions such as pinch-to-zoom, swiping to navigate and tapping to hide/show the navigation arrows and back button.

    I want to add a button that if clicked by the user, would display a UIActionSheet with options for sharing the photo by mail or MMS and the user would choose one of them.

    how ican do that?

  3. TS3016 says:

    i followed you Tutorial and i get an error at the Photo.m . I get the error at this point: @synthesize photoSource = _photoSource;

    file:…Photo.m: error: Automatic Reference Counting Issue: Existing ivar ‘_photoSource’ for unsafe_unretained property ‘photoSource’ must be __unsafe_unretained

    i tried to build the app following you tutorial with Xcode 4.2.1 for iOS 5. can you please make me new tutorial with the new SDK and maybe with storyboard. i want to add a photo gallery to my app. i want to display some photos from a website. it would be nice, if you could also implement googlemaps to show where the photo was taken. But mostly i have to know at first how to get three20 photo gallery in my app with sdk 5.

    i tried to download you complete project and its working fine and there is no error at that point. i don’t know how to fix that problem. i hope you can help me or you can make me a new tutorial.

    Thank You

    • xs2bush says:

      Hi thinesh,
      I see ur getting an arc error. You just need to change the retained properties to strong. As for writing another tutorial, im sorry i cant do that. Every tutorial is there to get ppl started so they can take help from it, not to do other’s work. You just explained your entire project for me to write a tutorial for! Its your work and nobody is going to do it for you. All the best for writing ur app urself. Happy Coding🙂

  4. TS3016 says:

    hi can anybody modify the Code for Xcode 4.2.1 SDK iOS 5? Is it possible to Upload the projekt here?

  5. TS3016 says:

    Hello,
    if i implement the Photo Gallery in my App, i want to go back to my app. how can i make a back button at the top of the TTThumbsViewController?

  6. TS3016 says:

    Hi,
    i have another question. i am using the TTThumbsViewController and i want to add a button at the top of the bar when one picture is shown. i mean at the view when i pick a photo from the thumbsview. i want to add a button at the view after picking the photo from the thumbsview, when only one picture is shown.

  7. NewPhonegapper says:

    Cant find a single tutorial on how to use this in ios5. Has me banging my head again the wall

  8. Casey says:

    Four things:

    1. PhotoSource should be called PhotoSet or make the static method return PhotoSource.
    2. When PhotoSource/PhotoSet extends TTURLRequestModel, you need to implement the TTPhotoSource delegate. So it should be @interface PhotoSet : TTURLRequestModel
    3. In the view controller have to implement self.photoSource = [PhotoSet samplePhotoSet]. If you just do photoSource = [PhotoSet samplePhotoSet] the table will not be initialized. I know you wrote it correctly but I messed up my implementation. Hopefully this may help some other people.
    4. You absolutely must implement the navigation controller or else clicking on the photo will not work.

  9. Amazing! Incredible step-by-step guide. I will utilize that will!

  10. please help me to add a back button in photoviewcontroler


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s