List User Controls for iOS sample code

Unofficial Content

The following Objective-C source code applies to this article, based on the KTPhotoBrowser project.

UCImageGalleryList.h file:

#import <Foundation/Foundation.h>
#import <GXFlexibleClient/GXFlexibleClient.h>
#import <MessageUI/MessageUI.h>
#import "KTThumbsViewController.h"
#import "KTPhotoBrowserDataSource.h"
#import "KTPhotoView.h"
#import "UCImageGalleryDataSource.h"
#import "UCImageGalleryDetail.h"

@interface UCImageGalleryList : GXControlGridBase <KTThumbsViewDataSource, UCImageGalleryDetailDelegate, UCImageGalleryDataSource, MFMailComposeViewControllerDelegate>
{
	NSArray *_photos; // The list of attributes of type Photo
	UCImageGalleryDetail *_detail;
	NSMutableArray *_selectedImageIndexes;
	BOOL _isSelecting;
}

@end

UCImageGalleryList.m file.

#import "KTPhotoBrowserDataSource.h"
#import "KTPhotoBrowserGlobal.h"
#import "KTPhotoView+SDWebImage.h"
#import "KTThumbView+SDWebImage.h"
#import "KTPhotoScrollViewController.h"
#import "UCImageGalleryList.h"

@interface UCImageGalleryList (Private)
- (void)loadData:(NSInteger)count;
- (NSInteger)numberOfPhotos;
- (void)reloadActionBarItems;
@end

#pragma mark -

@interface UCImageGalleryList (InternalProperties)

- (KTThumbsView *)thumbsView;

- (NSString *)dataAttribute;
- (NSString *)dataFieldSpecifier;
- (NSString *)titleAttribute;
- (NSString *)titleFieldSpecifier;
- (NSString *)subtitleAttribute;
- (NSString *)subtitleFieldSpecifier;
- (BOOL)enableShareAction;

@end

#pragma mark -

@interface UCImageGalleryList (Actions)
- (void)shareButtonTapped:(id)sender;
- (void)emailSelectedImages:(id)sender;
@end

#pragma mark -

@implementation UCImageGalleryList

#pragma mark - Internal properties

- (KTThumbsView *)thumbsView {
	return (KTThumbsView *)[self gridView];
}

- (NSString *)dataAttribute {
	NSString *propVal = [ [self properties] getPropertyValueString:@"@UCImageGalleryDataAtt"];

	if (!propVal) {
        for(id <GXEntityDataFieldDescriptor> fieldDescriptor in [ [self.entityDataListProvider entityDataDescriptor] entityDataFieldDescriptors]) {
            
            id <GXEntityDataFieldInfo> fInfo = [fieldDescriptor entityDataFieldInfo];
            if (([fInfo entityDataFieldInfoSpecialDomain] == GXSpecialDomainPhoto)
                || ([fInfo entityDataFieldInfoDataType] == GXDataTypeBitmap))
            {
                propVal = [fieldDescriptor entityDataFieldName];
                break;
            }
        }
	}
	
	return propVal;
}

- (NSString *)dataFieldSpecifier {
	return [ [self properties] getPropertyValueString:@"@UCImageGalleryDataField"];
}

- (NSString *)titleAttribute {
	return [ [self properties] getPropertyValueString:@"@UCImageGalleryTitleAtt"];
}

- (NSString *)titleFieldSpecifier {
	return [ [self properties] getPropertyValueString:@"@UCImageGalleryTitleField"];
}

- (NSString *)subtitleAttribute {
	return [ [self properties] getPropertyValueString:@"@UCImageGallerySubtitleAtt"];
}

- (NSString *)subtitleFieldSpecifier {
	return [ [self properties] getPropertyValueString:@"@UCImageGallerySubtitleField"];
}

- (BOOL)enableShareAction {
	return [ [self properties] getPropertyValueBool:@"@UCImageGalleryEnableShare"];
}


- (UIView *)newGridViewWithFrame:(CGRect)frame {
	KTThumbsView *thumbs = [ [KTThumbsView alloc] initWithFrame:frame];
	[thumbs setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
	[thumbs setScrollsToTop:YES];
	[thumbs setScrollEnabled:YES];
	[thumbs setAlwaysBounceVertical:YES];
	[thumbs setBackgroundColor:[UIColor clearColor]];
	[thumbs setDataSource:self];
	[thumbs setTag:NSIntegerMax];
	
	if (CURRENT_DEVICE_IPAD) {
		[thumbs setThumbSize:CGSizeMake(120, 120)];
	}
	else {
		[thumbs setThumbSize:CGSizeMake(75, 75)];
	}
    
	return thumbs;
}

- (void)reloadData {
	[self loadData:[ [self entityDataListProvider] numberOfLoadedEntitiesInSection:0]];
}


- (NSString *)titleForImageAtIndex:(NSInteger)index {
	return [self fieldValueAtIndex:index
					  forFieldName:[self titleAttribute]
					fieldSpecifier:[self titleFieldSpecifier]
			];
}

- (NSString *)captionForImageAtIndex:(NSInteger)index {
	return [self fieldValueAtIndex:index
					  forFieldName:[self subtitleAttribute]
					fieldSpecifier:[self subtitleFieldSpecifier]
			];
}

#pragma mark - KTThumbsViewDataSource

- (NSInteger)thumbsViewNumberOfThumbs:(KTThumbsView *)thumbsView {
	return [self numberOfPhotos];
}

- (KTThumbView *)thumbsView:(KTThumbsView *)thumbsView thumbForIndex:(NSInteger)index {
	KTThumbView *thumbView = [thumbsView dequeueReusableThumbView];
	if (!thumbView) {
		thumbView = [ [ [KTThumbView alloc] initWithFrame:CGRectZero] autorelease];
	}
	
	[thumbView  setController:self];

	// Set thumbnail image asynchronously.
    if (index < [_photos count]) {
        NSURL *url = [_photos objectAtIndex:index];
        if ([url isKindOfClass:[NSNull class]]) {
            [thumbView setImage:[GXUtilities placeHolderImage] forState:UIControlStateNormal];
        }
        else {
            [thumbView setImageWithURL:url placeholderImage:[GXUtilities placeHolderImage]];
        }
    }
    else {
		if (_autoLoad) {
			//[thumbView setActivityIndicatorVisible:YES];
			[thumbView setImage:nil forState:UIControlStateNormal];
			NSInteger totalCount = [ [self entityDataListProvider] totalCount];
			switch (totalCount) {
				case kTotalCountUnknownLocal:
				case kTotalCountUnknownRemote:
				case kTotalCountUnknownPendingRemote:
					[ [self entityDataListProvider] load];
					break;
				default:
					break;
			}
		}
		else {
			[thumbView setImage:KTLoadImageFromBundle(@"refreshIcon.png") forState:UIControlStateNormal];
		}
    }
	
	BOOL isSelected = NO;
	for (NSNumber *num in _selectedImageIndexes) {
		if ([num unsignedIntValue] == index) {
			isSelected = YES;
			break;
		}
	}
	[thumbView setSelected:isSelected];
	
	return thumbView;
}

- (void)didSelectThumbAtIndex:(NSUInteger)index {
	if (index >= [_photos count]) {
		_autoLoad = YES;
		[ [self entityDataListProvider] load];
		return;
	}
	
	if (_isSelecting) {
		UIView *thumb = [ [self thumbsView] viewWithTag:index];
		BOOL selected = NO;
		if ([thumb isKindOfClass:[KTThumbView class]]) {
			selected = ![(KTThumbView *)thumb isSelected];
			[(KTThumbView *)thumb setSelected:selected];
		}
		
		if (selected) {
			[_selectedImageIndexes addObject:[NSNumber numberWithUnsignedInt:index]];
		}
		else {
			for (NSNumber *num in _selectedImageIndexes) {
				if (index == [num unsignedIntValue]) {
					[_selectedImageIndexes removeObject:num];
					break;
				}
			}
		}
	}
	else {
		if (TRUE) {
			
			if (!_detail) {
				_detail = [ [UCImageGalleryDetail alloc] initWithFrame:CGRectZero];
				[_detail setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
				[_detail setDataSource:self];
				[_detail setDelegate:self];
			}
			
			[ [self view] addSubview:_detail];
			
			[UIView animateWithDuration:0.3f
							 animations:^ {
								 [_detail setFrame:self.view.bounds];
							 }
							 completion:^(BOOL finished) {
								 [_detail setCurrentIndex:index];
								 [_detail scrollToCurrentIndex];
								 [self reloadActionBarItems];
							 }
			 ];
		}
		else {
			[self presentDetailViewForPhotoAtIndex:index];
		}
	}
}

#pragma mark - Private

- (void)loadData:(NSInteger)count{
    // This method reads the information of the entities and extracts the images
    NSMutableArray *tmpPhotos = [ [NSMutableArray alloc] initWithCapacity:count];
	
    NSString *photoAttName = [self dataAttribute];
	NSString *photoFieldSpecifier = [self dataFieldSpecifier];
    if (photoAttName){
        for(NSUInteger i = 0; i < count; i++) {
			NSString *value = [self valueForEntityDataFieldName:photoAttName 
                          fieldSpecifier:photoFieldSpecifier indexPath:[NSIndexPath indexPathForRow:i inSection:0]
							   ];
            NSURL *url = [GXEntityHelper urlFromFieldValue:value];
            if (url) {
                [tmpPhotos addObject:url];
            }
            else {
                [tmpPhotos addObject:[NSNull null]];
            }
		}
	}

	_photos = [tmpPhotos retain];

    [tmpPhotos release];
    
	[ [self thumbsView] reloadData];
}

- (NSInteger)numberOfPhotos {
    NSInteger numberOfRows = 0;
    NSInteger totalCount = [self.entityDataListProvider totalCount];
    
    if (totalCount == kTotalCountUnknownLocal) {
        numberOfRows = 1;
    }
    else {
        numberOfRows = [self.entityDataListProvider numberOfLoadedEntitiesInSection:0];
        switch (totalCount) {
            case kTotalCountUnknownRemote:
            case kTotalCountUnknownPendingRemote:
            {
                numberOfRows++; // "Load More..." or "Loading..." Cell
                break;
            }
            default:
            {
                if (totalCount > numberOfRows)
                    numberOfRows++; // "Load More..." or "Loading..." Cell
                break;
            }
        }
    }
    
	return numberOfRows;
}

- (void)reloadActionBarItems {
	[ [ [self context] controlWithActionBarItemsDelegate] controlWithActionBarItemsNeedsReloadActions:self];
}

#pragma mark - UCImageGalleryDataSource

- (NSUInteger)count {
	return [_photos count];
}

- (NSURL *)photoURLAtIndex:(NSUInteger)index {
	return [_photos objectAtIndex:index];
}

- (NSString *)titleAtIndex:(NSUInteger)index {
	return [self titleForImageAtIndex:index];
}

- (NSString *)captionAtIndex:(NSUInteger)index {
	return [self captionForImageAtIndex:index];
}

#pragma mark - GXControlWithActionBarItems

- (NSArray *)controlRequiredNavigationBarRightItems {
	NSArray *actionBarItemsArray = nil;
	if ([self enableShareAction]) {
		UIBarButtonSystemItem buttonSystemItem = _isSelecting ? UIBarButtonSystemItemDone : UIBarButtonSystemItemAction;
		UIBarButtonItem *barButton = _isSelecting ? [ [UIBarButtonItem alloc] initWithTitle:[GXResources translationFor:@"GXM_Done"]
                                                                                     style:UIBarButtonItemStyleDone
                                                                                    target:self
                                                                                    action:@selector(shareButtonTapped:)]
        : [ [UIBarButtonItem alloc] initWithBarButtonSystemItem:buttonSystemItem
                                                        target:self
                                                        action:@selector(shareButtonTapped:)
           ];
		actionBarItemsArray = [NSArray arrayWithObject:barButton];
		[barButton release];
	} 
	return actionBarItemsArray;
}

#pragma mark - Actions

- (void)shareButtonTapped:(id)sender {
	if (_detail) {
		[_selectedImageIndexes removeAllObjects];
		[_selectedImageIndexes addObject:[NSNumber numberWithUnsignedInt:[_detail currentIndex]]];
		
		[self emailSelectedImages:sender];
	}
	else {
		if (_isSelecting) {
			[self emailSelectedImages:sender];
            
		}
		_isSelecting = !_isSelecting;
		[self reloadActionBarItems];
	}
}

- (void)emailSelectedImages:(id)sender {
	if ([_selectedImageIndexes count]) {
		MFMailComposeViewController *picker = [ [MFMailComposeViewController alloc] init];
		picker.mailComposeDelegate = self;
		
		[picker setMessageBody:@"" isHTML:NO];
		
		for (int index = 0; index < _selectedImageIndexes.count; index++) {
			NSUInteger photoIndex = [ [_selectedImageIndexes objectAtIndex:index] unsignedIntValue];
			UIImage *image = [ [SDWebImageManager2 sharedManager] imageWithURL:[_photos objectAtIndex:photoIndex]];
			NSData *data = UIImagePNGRepresentation(image);
			NSString *imageName = [NSString stringWithFormat:@"SelectedImage_%d", index];
			[picker addAttachmentData:data mimeType:@"image/png" fileName:imageName];
			
			UIView *thumb = [ [self thumbsView] viewWithTag:photoIndex];
			if ([thumb isKindOfClass:[KTThumbView class]]) {
				[(KTThumbView *)thumb setSelected:NO];
			}
		}
		
		[ [ [self context] entityViewController] presentModalViewController:picker animated:YES];
		
		[picker release];
		
		[_selectedImageIndexes removeAllObjects];
	}
}

#pragma mark - MFMailComposeViewControllerDelegate

- (void)mailComposeController:(MFMailComposeViewController *)controller
		  didFinishWithResult:(MFMailComposeResult)result
						error:(NSError*)error 
{	
	[ [ [self context] entityViewController] dismissModalViewControllerAnimated:YES];
}

#pragma mark - Memory management

- (void)dealloc {
	[_selectedImageIndexes release], _selectedImageIndexes = nil;
    [_photos release], _photos = nil;
	[_detail release], _detail = nil;
    [super dealloc];
}

@end

UCImageGalleryDataSource.h file

#import <Foundation/Foundation.h>
@protocol UCImageGalleryDataSource <NSObject>
- (NSUInteger)count;
- (NSURL *)photoURLAtIndex:(NSUInteger)index;
- (NSString *)titleAtIndex:(NSUInteger)index;
- (NSString *)captionAtIndex:(NSUInteger)index;
@end

UCImageGalleryDetail.h file

#import <UIKit/UIKit.h>
#import <GXFlexibleClient/GXFlexibleClient.h>
#import "UCImageGalleryDataSource.h"
#import "KTPhotoView.h"

@class UCImageGalleryDetail;

#pragma mark -

@protocol UCImageGalleryDetailDelegate <NSObject>

- (void)ucimageGalleryDetailDidTapCloseButton:(UCImageGalleryDetail *)detail;
- (void)ucimageGalleryDetail:(UCImageGalleryDetail *)detail didTapImageAtIndex:(NSInteger)index;

@end

#pragma mark -

@interface UCImageGalleryDetail : UIView <UIScrollViewDelegate> {
	UIScrollView *_scrollView;
	UIToolbar *_toolbar;
	UIButton *_closeButton;
	UILabel *_titleView;
	UILabel *_captionView;
	
	id<UCImageGalleryDataSource> _dataSource;
	
	id<UCImageGalleryDetailDelegate> _delegate;
	
	NSUInteger _currentIndex;
	
	NSMutableArray *_photoViews;
}

@property (nonatomic, assign) id<UCImageGalleryDataSource> dataSource;
@property (nonatomic, assign) NSUInteger currentIndex;
@property (nonatomic, assign) id<UCImageGalleryDetailDelegate> delegate;

- (void)scrollToCurrentIndex;

@end

UCImageGalleryDetail.m file

#import "UCImageGalleryDetail.h"
#import "KTPhotoView.h"
#import "KTPhotoView+SDWebImage.h"
#import "KTPhotoBrowserGlobal.h"

#define kCaptionPadding 10
#define kCloseButtonWidth 44
#define kCloseButtonHeight 44
#define kDefaultLineHeight 21

@interface UCImageGalleryDetail (Private)
- (void)setScrollViewContentSize;
- (void)createPhotoViewsCache;
- (void)scrollToIndex:(NSInteger)index;
- (void)loadPhoto:(NSInteger)index;
- (void)unloadPhoto:(NSInteger)index;
- (CGRect)frameForPageAtIndex:(NSUInteger)index;
- (void)setTitleWithCurrentPhotoIndex;
- (void)setCaptionWithCurrentPhotoIndex;
- (UILabel *)newLabel;
@end

#pragma mark -

@interface UCImageGalleryDetail (ActionHandlers)
- (void)closeView:(id)sender;
@end

#pragma mark -

@implementation UCImageGalleryDetail

#pragma mark - Init & dealloc

- (id)init {
	return [self initWithFrame:CGRectZero];
}

- (id)initWithFrame:(CGRect)frame
{
	self = [super initWithFrame:frame];
	if (self) {
		_dataSource = nil;
		
		_scrollView = [ [UIScrollView alloc] initWithFrame:frame];
		[_scrollView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
		[_scrollView setDelegate:self];
		[_scrollView setBackgroundColor:[UIColor blackColor]];
		[_scrollView setAutoresizesSubviews:YES];
		[_scrollView setPagingEnabled:YES];
		[_scrollView setShowsVerticalScrollIndicator:NO];
		[_scrollView setShowsHorizontalScrollIndicator:NO];
		[self addSubview:_scrollView];
		
		GXTheme *theme = [GXThemeHelper currentTheme];
		
		_titleView = [self newLabel];
		[_titleView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
		[_titleView setNumberOfLines:1];
		[_titleView applyThemeClass:[theme themeClassAttributeTitle]];
		[self addSubview:_titleView];
		
		_captionView = [self newLabel];
		[_captionView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin];
		[_captionView setNumberOfLines:2];
		[_captionView applyThemeClass:[theme themeClassAttributeSubtitle]];
		[self addSubview:_captionView];
		
		_closeButton = [ [UIButton buttonWithType:UIButtonTypeCustom] retain];
		[_closeButton setImage:KTLoadImageFromBundle(@"closeIcon.png") forState:UIControlStateNormal];
		[_closeButton addTarget:self action:@selector(closeView:) forControlEvents:UIControlEventTouchUpInside];
		[_closeButton setFrame:CGRectMake(frame.size.width - kCloseButtonWidth,
										  0,
										  kCloseButtonWidth,
										  kCloseButtonHeight)
		 ];
		[_closeButton setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin];
		[self addSubview:_closeButton];
	}
	return self;
}

- (UILabel *)newLabel {
	UILabel *label = [ [UILabel alloc] initWithFrame:CGRectZero];
	[label setBackgroundColor:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f]];
	[label setTextColor:[UIColor whiteColor]];
	[label setTextAlignment:UITextAlignmentCenter];
	return label;
}

- (void)dealloc {
	[_scrollView release], _scrollView = nil;
	[_closeButton release], _closeButton = nil;
	[_titleView release], _titleView = nil;
	[_captionView release], _captionView = nil;
	[_photoViews release], _photoViews = nil;
	[super dealloc];
}

#pragma mark - Properties

@synthesize dataSource = _dataSource;

- (void)setDataSource:(id<UCImageGalleryDataSource>)dataSource {
	_dataSource = dataSource;
	[self createPhotoViewsCache];
}

@synthesize currentIndex = _currentIndex;

- (void)setCurrentIndex:(NSUInteger)index {
	_currentIndex = index;
    
	[self loadPhoto:index];
	[self loadPhoto:index + 1];
	[self loadPhoto:index - 1];
	[self unloadPhoto:index + 2];
	[self unloadPhoto:index - 2];
    
	[self setTitleWithCurrentPhotoIndex];
	[self setCaptionWithCurrentPhotoIndex];
}

@synthesize delegate = _delegate;

#pragma mark - Public

- (void)scrollToCurrentIndex {
	[self setScrollViewContentSize];
	[self scrollToIndex:_currentIndex];
}

#pragma mark - UIView

- (void)layoutSubviews {
	[super layoutSubviews];
	[_scrollView setDelegate:nil];	// don't want the rotation to change the current page
	CGRect bounds = self.bounds;
	for (int index = 0; index < [_photoViews count]; index++) {
		id item = [_photoViews objectAtIndex:index];
		if ([item isKindOfClass:[UIView class]]) {
			UIView *view = (UIView *)item;
			CGRect viewFrame = CGRectMake(index * bounds.size.width,
										  0,
										  bounds.size.width,
										  bounds.size.height);
			[view setFrame:viewFrame];
		}
	}
	[_scrollView setDelegate:self];
}

- (void)setFrame:(CGRect)frame {
	[_scrollView setDelegate:nil];	// don't want the rotation to change the current page
    
	[super setFrame:frame];
    
	if (!CGSizeEqualToSize(CGSizeZero, frame.size)) {
		for (int index = 0; index < [_photoViews count]; index++) {
			id item = [_photoViews objectAtIndex:index];
			if ([item isKindOfClass:[UIView class]]) {
				UIView *view = (UIView *)item;
				CGRect viewFrame = CGRectMake(index * frame.size.width,
											  0,
											  frame.size.width,
											  frame.size.height);
				[view setFrame:viewFrame];
			}
		}
		
		
		[self scrollToCurrentIndex];
	}
	
	[_scrollView setDelegate:self];
}

#pragma mark - UIScrollViewDelegate

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
	CGFloat pageWidth = scrollView.frame.size.width;
	float fractionalPage = scrollView.contentOffset.x / pageWidth;
	
	NSInteger page = floor(fractionalPage);
	if (page < 0) {
		page = 0;
	}
	
	if (page != _currentIndex) {
		[self setCurrentIndex:page];
	}
}

#pragma mark - KTPhotoViewTouchDelegate

- (void)photoViewDidReceiveTouch:(KTPhotoView *)photoView {
	[_delegate ucimageGalleryDetail:self didTapImageAtIndex:photoView.index];
}

#pragma mark - Private

- (void)setScrollViewContentSize {
	NSInteger pageCount = [_dataSource count];
	if (pageCount == 0) {
		pageCount = 1;
	}
    
	CGSize size = CGSizeMake(_scrollView.frame.size.width * pageCount, 
							 _scrollView.frame.size.height / 2);   // Cut in half to prevent horizontal scrolling.
	[_scrollView setContentSize:size];
}

- (void)createPhotoViewsCache {
	if (_photoViews) {
		[_photoViews release];
	}
	
	NSUInteger photoCount = [_dataSource count];
	_photoViews = [ [NSMutableArray alloc] initWithCapacity:photoCount];
	for (int i=0; i < photoCount; i++) {
		[_photoViews addObject:[NSNull null]];
	}
}

- (void)scrollToIndex:(NSInteger)index {
	CGRect frame = _scrollView.frame;
	frame.origin.x = frame.size.width * index;
	frame.origin.y = 0;
	[_scrollView scrollRectToVisible:frame animated:NO];
}

- (void)loadPhoto:(NSInteger)index {
	if (index < 0 || index >= [_dataSource count]) {
		return;
	}
	
	if (!_photoViews) {
		[self createPhotoViewsCache];
	}
	
	id currentPhotoView = [_photoViews objectAtIndex:index];
	if (NO == [currentPhotoView isKindOfClass:[KTPhotoView class]]) {
		// Load the photo view.
		CGRect frame = [self frameForPageAtIndex:index];
		KTPhotoView *photoView = [ [KTPhotoView alloc] initWithFrame:frame];
		[photoView setIndex:index];
		[photoView setBackgroundColor:[UIColor clearColor]];
		
		// Set the photo image.
		if (index < [_dataSource count]) {
			NSURL *url = [_dataSource photoURLAtIndex:index];
			if ([url isKindOfClass:[NSNull class]]) {
				[photoView setImage:[GXUtilities placeHolderImage]];
			}
			else {
				[photoView setImageWithURL:url placeholderImage:[GXUtilities placeHolderImage]];
			}
		}
		
		[_scrollView addSubview:photoView];
		[_photoViews replaceObjectAtIndex:index withObject:photoView];
		[photoView release];
	} else {
		// Turn off zooming.
		[currentPhotoView turnOffZoom];
	}
}

- (void)unloadPhoto:(NSInteger)index {
	if (index < 0 || index >= [_dataSource count]) {
		return;
	}
	
	id currentPhotoView = [_photoViews objectAtIndex:index];
	if ([currentPhotoView isKindOfClass:[KTPhotoView class]]) {
		[currentPhotoView removeFromSuperview];
		[_photoViews replaceObjectAtIndex:index withObject:[NSNull null]];
	}
}

- (CGRect)frameForPageAtIndex:(NSUInteger)index {
	CGRect bounds = [_scrollView bounds];
	CGRect pageFrame = bounds;
	pageFrame.origin.x = bounds.size.width * index;
	return pageFrame;
}

- (void)setTitleWithCurrentPhotoIndex {
	NSString *formatString = NSLocalizedString(@"%1$i of %2$i", @"Picture X out of Y total.");
    
	NSString *title = [_dataSource titleAtIndex:_currentIndex];
	title = title ? : [NSString stringWithFormat:formatString, _currentIndex + 1, [_dataSource count], nil];
    
	CGRect frame = self.bounds;
    
	[_titleView setFrame:CGRectMake(0, 0, frame.size.width, kDefaultLineHeight)];
	[_titleView setText:title];
}

- (void) setCaptionWithCurrentPhotoIndex {
	NSString *caption = [_dataSource captionAtIndex:_currentIndex];
	
	if (caption) {
		CGRect frame = self.bounds;
		
		CGFloat maxHeight = [_captionView numberOfLines] * kDefaultLineHeight + kCaptionPadding * 2;
		
		CGSize maximumSize = CGSizeMake(frame.size.width, maxHeight);
		UIFont *font = _captionView.font;
		CGSize lSize = [caption sizeWithFont:font constrainedToSize:maximumSize];
		lSize.width = frame.size.width;
		lSize.height += kCaptionPadding * 2;
		
		if (lSize.height > maxHeight) {
			lSize.height = maxHeight;
		}
		
		CGFloat toolbarOriginY = frame.size.height;
		[_captionView setFrame:CGRectMake(0,
										  toolbarOriginY - lSize.height,
										  frame.size.width,
										  lSize.height
										  )
		 ];
		[_captionView setText:caption];
	}
}

#pragma mark - ActionHandlers

- (void)closeView:(id)sender {
	if (_delegate) {
		[_delegate  ucimageGalleryDetailDidTapCloseButton:self];
	}
	else {
		[self removeFromSuperview];
	}
	
	// unload all photos in cache
	for (int index = 0; index < [_dataSource count]; index++) {
		[self unloadPhoto:index];
	}
}

@end