Custom data models can now be used to represent photo objects as long as they conform to the new MWPhoto protocol.

Thanks to @adamjernst for the feature suggestion!
This commit is contained in:
Michael Waterfall
2012-01-02 14:53:13 +01:00
parent 83afc01ed8
commit 0ec18e9ba7
12 changed files with 278 additions and 223 deletions

View File

@@ -7,16 +7,12 @@
// //
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "MWPhotoProtocol.h"
@class MWPhoto; @interface MWCaptionView : UIView
@interface MWCaptionView : UIView {
MWPhoto *_photo;
UILabel *_label;
}
// Init // Init
- (id)initWithPhoto:(MWPhoto *)photo; - (id)initWithPhoto:(id<MWPhoto>)photo;
// To create your own custom caption view, subclass this view // To create your own custom caption view, subclass this view
// and override the following two methods (as well as any other // and override the following two methods (as well as any other

View File

@@ -11,9 +11,16 @@
static const CGFloat labelPadding = 10; static const CGFloat labelPadding = 10;
// Private
@interface MWCaptionView () {
id<MWPhoto> _photo;
UILabel *_label;
}
@end
@implementation MWCaptionView @implementation MWCaptionView
- (id)initWithPhoto:(MWPhoto *)photo { - (id)initWithPhoto:(id<MWPhoto>)photo {
self = [super initWithFrame:CGRectMake(0, 0, 320, 44)]; // Random initial frame self = [super initWithFrame:CGRectMake(0, 0, 320, 44)]; // Random initial frame
if (self) { if (self) {
_photo = [photo retain]; _photo = [photo retain];
@@ -40,7 +47,6 @@ static const CGFloat labelPadding = 10;
self.bounds.size.height)]; self.bounds.size.height)];
_label.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; _label.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
_label.opaque = NO; _label.opaque = NO;
_label.text = _photo.caption ? _photo.caption : @"[No Title]";
_label.backgroundColor = [UIColor clearColor]; _label.backgroundColor = [UIColor clearColor];
_label.textAlignment = UITextAlignmentCenter; _label.textAlignment = UITextAlignmentCenter;
_label.lineBreakMode = UILineBreakModeWordWrap; _label.lineBreakMode = UILineBreakModeWordWrap;
@@ -48,6 +54,12 @@ static const CGFloat labelPadding = 10;
_label.textColor = [UIColor whiteColor]; _label.textColor = [UIColor whiteColor];
_label.shadowColor = [UIColor blackColor]; _label.shadowColor = [UIColor blackColor];
_label.shadowOffset = CGSizeMake(1, 1); _label.shadowOffset = CGSizeMake(1, 1);
// Caption
if ([_photo respondsToSelector:@selector(caption)]) {
_label.text = [_photo caption] ? [_photo caption] : @"[No Title]";
}
[self addSubview:_label]; [self addSubview:_label];
} }

View File

@@ -7,36 +7,18 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "MWPhotoProtocol.h"
#import "SDWebImageDecoder.h" #import "SDWebImageDecoder.h"
#import "SDWebImageManager.h" #import "SDWebImageManager.h"
@class MWPhoto; // This class models a photo/image and it's caption
// If you want to handle photos, caching, decompression
@protocol MWPhotoDelegate <NSObject> // yourself then you can simply ensure your custom data model
- (void)photoDidFinishLoading:(MWPhoto *)photo; // conforms to MWPhotoProtocol
- (void)photoDidFailToLoad:(MWPhoto *)photo; @interface MWPhoto : NSObject <MWPhoto, SDWebImageManagerDelegate, SDWebImageDecoderDelegate>
@end
@interface MWPhoto : NSObject <SDWebImageManagerDelegate, SDWebImageDecoderDelegate> {
// Image Sources
NSString *_photoPath;
NSURL *_photoURL;
// Image
UIImage *_underlyingImage;
// Other
NSString *_caption;
// Delegate
id <MWPhotoDelegate> _photoLoadingDelegate;
}
// Properties // Properties
@property (nonatomic, retain) NSString *caption; @property (nonatomic, retain) NSString *caption;
@property (nonatomic, retain) id <MWPhotoDelegate> photoLoadingDelegate;
// Class // Class
+ (MWPhoto *)photoWithImage:(UIImage *)image; + (MWPhoto *)photoWithImage:(UIImage *)image;
@@ -48,10 +30,5 @@
- (id)initWithFilePath:(NSString *)path; - (id)initWithFilePath:(NSString *)path;
- (id)initWithURL:(NSURL *)url; - (id)initWithURL:(NSURL *)url;
// Public methods
- (BOOL)isImageAvailable; // Checks if underlying image is available
- (UIImage *)image; // Access underlying image
- (void)loadImageAndNotify:(id<MWPhotoDelegate>)delegate; // Load image from source (file or web and notifies delegate
- (void)releasePhoto; // Release underlying (large decompressed) image
@end @end

View File

@@ -10,9 +10,28 @@
#import "MWPhotoBrowser.h" #import "MWPhotoBrowser.h"
// Private // Private
@interface MWPhoto () @interface MWPhoto () {
@property (retain) UIImage *underlyingImage;
- (void)imageDidFinishLoading; // Image Sources
NSString *_photoPath;
NSURL *_photoURL;
// Image
UIImage *_underlyingImage;
// Other
NSString *_caption;
BOOL _loadingInProgress;
}
// Properties
@property (nonatomic, retain) UIImage *underlyingImage;
// Methods
- (void)imageDidFinishLoadingSoDecompress;
- (void)imageLoadingComplete;
@end @end
// MWPhoto // MWPhoto
@@ -20,7 +39,6 @@
// Properties // Properties
@synthesize underlyingImage = _underlyingImage, @synthesize underlyingImage = _underlyingImage,
photoLoadingDelegate = _photoLoadingDelegate,
caption = _caption; caption = _caption;
#pragma mark Class Methods #pragma mark Class Methods
@@ -66,63 +84,56 @@ caption = _caption;
[_photoPath release]; [_photoPath release];
[_photoURL release]; [_photoURL release];
[_underlyingImage release]; [_underlyingImage release];
[_photoLoadingDelegate release];
[super dealloc]; [super dealloc];
} }
#pragma mark Photo #pragma mark MWPhoto Protocol Methods
// Release if we can get it again from path or url - (UIImage *)underlyingImage {
- (void)releasePhoto { return _underlyingImage;
self.photoLoadingDelegate = nil;
[[SDWebImageManager sharedManager] cancelForDelegate:self];
if (self.underlyingImage && (_photoPath || _photoURL)) {
self.underlyingImage = nil;
}
} }
// Return whether the image available - (void)loadUnderlyingImageAndNotify {
// It is available if the UIImage has been loaded and NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread.");
// loading from file or URL is not required _loadingInProgress = YES;
- (BOOL)isImageAvailable {
return (self.underlyingImage != nil);
}
- (UIImage *)image {
return self.underlyingImage;
}
// Called on main
- (void)loadImageAndNotify:(id<MWPhotoDelegate>)delegate {
if (_photoLoadingDelegate) return;
if (self.underlyingImage) { if (self.underlyingImage) {
// Done // Image already loaded
[delegate photoDidFinishLoading:self]; [self imageLoadingComplete];
} else { } else {
if (_photoPath) { if (_photoPath) {
// Load async from file // Load async from file
self.photoLoadingDelegate = delegate;
[self performSelectorInBackground:@selector(loadImageFromFileAsync) withObject:nil]; [self performSelectorInBackground:@selector(loadImageFromFileAsync) withObject:nil];
} else if (_photoURL) { } else if (_photoURL) {
self.photoLoadingDelegate = delegate;
// Load async from web (using SDWebImage) // Load async from web (using SDWebImage)
SDWebImageManager *manager = [SDWebImageManager sharedManager]; SDWebImageManager *manager = [SDWebImageManager sharedManager];
UIImage *cachedImage = [manager imageWithURL:_photoURL]; UIImage *cachedImage = [manager imageWithURL:_photoURL];
if (cachedImage) { if (cachedImage) {
// Use the cached image immediatly // Use the cached image immediatly
self.underlyingImage = cachedImage; self.underlyingImage = cachedImage;
[self imageDidFinishLoading]; [self imageDidFinishLoadingSoDecompress];
} else { } else {
// Start an async download // Start an async download
[manager downloadWithURL:_photoURL delegate:self]; [manager downloadWithURL:_photoURL delegate:self];
} }
} else { } else {
// Failed // Failed - no source
[delegate photoDidFailToLoad:self]; self.underlyingImage = nil;
[self imageLoadingComplete];
} }
} }
} }
// Release if we can get it again from path or url
- (void)unloadUnderlyingImage {
_loadingInProgress = NO;
[[SDWebImageManager sharedManager] cancelForDelegate:self];
if (self.underlyingImage && (_photoPath || _photoURL)) {
self.underlyingImage = nil;
}
}
#pragma mark - Async Loading
// Called in background // Called in background
// Load image in background from local file // Load image in background from local file
- (void)loadImageFromFileAsync { - (void)loadImageFromFileAsync {
@@ -136,53 +147,53 @@ caption = _caption;
self.underlyingImage = nil; self.underlyingImage = nil;
MWLog(@"Photo from file error: %@", error); MWLog(@"Photo from file error: %@", error);
} }
[self performSelectorOnMainThread:@selector(imageDidFinishLoading) withObject:nil waitUntilDone:NO];
} @catch (NSException *exception) { } @catch (NSException *exception) {
} @finally { } @finally {
[self performSelectorOnMainThread:@selector(imageDidFinishLoadingSoDecompress) withObject:nil waitUntilDone:NO];
[pool drain]; [pool drain];
} }
} }
// Called on main // Called on main
- (void)imageDidFinishLoading { - (void)imageDidFinishLoadingSoDecompress {
NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread.");
if (self.underlyingImage) { if (self.underlyingImage) {
// Decode image to avoid lagging when UIKit lazy loads // Decode image async to avoid lagging when UIKit lazy loads
// Happens async
[[SDWebImageDecoder sharedImageDecoder] decodeImage:self.underlyingImage withDelegate:self userInfo:nil]; [[SDWebImageDecoder sharedImageDecoder] decodeImage:self.underlyingImage withDelegate:self userInfo:nil];
} else { } else {
// Failed // Failed
[_photoLoadingDelegate photoDidFailToLoad:self]; [self imageLoadingComplete];
self.photoLoadingDelegate = nil;
} }
} }
- (void)imageLoadingComplete {
NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread.");
// Complete so notify
_loadingInProgress = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_LOADING_DID_END_NOTIFICATION
object:self];
}
#pragma mark - SDWebImage Delegate #pragma mark - SDWebImage Delegate
// Called on main // Called on main
- (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image { - (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)image {
self.underlyingImage = image; self.underlyingImage = image;
[self imageDidFinishLoading]; [self imageDidFinishLoadingSoDecompress];
} }
// Called on main // Called on main
- (void)webImageManager:(SDWebImageManager *)imageManager didFailWithError:(NSError *)error { - (void)webImageManager:(SDWebImageManager *)imageManager didFailWithError:(NSError *)error {
self.underlyingImage = nil; self.underlyingImage = nil;
MWLog(@"SDWebImage failed to download image: %@", error); MWLog(@"SDWebImage failed to download image: %@", error);
[self imageDidFinishLoading]; [self imageDidFinishLoadingSoDecompress];
} }
// Called on main // Called on main
- (void)imageDecoder:(SDWebImageDecoder *)decoder didFinishDecodingImage:(UIImage *)image userInfo:(NSDictionary *)userInfo { - (void)imageDecoder:(SDWebImageDecoder *)decoder didFinishDecodingImage:(UIImage *)image userInfo:(NSDictionary *)userInfo {
if (image) { // Finished compression so we're complete
// Complete self.underlyingImage = image;
self.underlyingImage = image; [self imageLoadingComplete];
[_photoLoadingDelegate photoDidFinishLoading:self];
} else {
// Fail
self.underlyingImage = nil;
[_photoLoadingDelegate photoDidFailToLoad:self];
}
self.photoLoadingDelegate = nil;
} }
@end @end

View File

@@ -9,60 +9,29 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h> #import <MessageUI/MessageUI.h>
#import "MWPhoto.h" #import "MWPhoto.h"
#import "MWPhotoProtocol.h"
#import "MWCaptionView.h" #import "MWCaptionView.h"
// Debug Logging // Debug Logging
#if 0 // Set to 1 to enable debug logging #if 1 // Set to 1 to enable debug logging
#define MWLog(x, ...) NSLog(x, ## __VA_ARGS__); #define MWLog(x, ...) NSLog(x, ## __VA_ARGS__);
#else #else
#define MWLog(x, ...) #define MWLog(x, ...)
#endif #endif
@protocol MWPhotoBrowserDelegate; // Delgate
@class MBProgressHUD; @class MWPhotoBrowser;
@protocol MWPhotoBrowserDelegate <NSObject>
- (NSUInteger)numberOfPhotosInPhotoBrowser:(MWPhotoBrowser *)photoBrowser;
- (id<MWPhoto>)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index;
@optional
- (MWCaptionView *)photoBrowser:(MWPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index;
@end
@interface MWPhotoBrowser : UIViewController <UIScrollViewDelegate, MWPhotoDelegate, UIActionSheetDelegate, MFMailComposeViewControllerDelegate> { // MWPhotoBrowser
@interface MWPhotoBrowser : UIViewController <UIScrollViewDelegate, UIActionSheetDelegate, MFMailComposeViewControllerDelegate>
// Data
id <MWPhotoBrowserDelegate> _delegate;
NSUInteger _photoCount;
NSMutableArray *_photos;
NSArray *_depreciatedPhotoData; // Depreciated
// Views
UIScrollView *_pagingScrollView;
// Paging
NSMutableSet *_visiblePages, *_recycledPages;
NSUInteger _currentPageIndex;
NSUInteger _pageIndexBeforeRotation;
// Navigation & controls
UIToolbar *_toolbar;
NSTimer *_controlVisibilityTimer;
UIBarButtonItem *_previousButton, *_nextButton, *_actionButton;
UIActionSheet *_actionsSheet;
MBProgressHUD *_progressHUD;
// Appearance
UIImage *_navigationBarBackgroundImageDefault,
*_navigationBarBackgroundImageLandscapePhone;
UIColor *_previousNavBarTintColor;
UIBarStyle _previousNavBarStyle;
UIStatusBarStyle _previousStatusBarStyle;
// Misc
BOOL _displayActionButton;
BOOL _performingLayout;
BOOL _rotating;
BOOL _viewIsActive; // active as in it's in the view heirarchy
BOOL _didSavePreviousStateOfNavBar;
BOOL _loadAdjacentWhenCurrentPhotoHasLoaded;
}
// Properties // Properties
@property (nonatomic, assign) id <MWPhotoBrowserDelegate> delegate;
@property (nonatomic) BOOL displayActionButton; @property (nonatomic) BOOL displayActionButton;
// Init // Init
@@ -77,10 +46,4 @@
@end @end
// Delgate
@protocol MWPhotoBrowserDelegate <NSObject>
- (NSUInteger)numberOfPhotosInPhotoBrowser:(MWPhotoBrowser *)photoBrowser;
- (MWPhoto *)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index;
@optional
- (MWCaptionView *)photoBrowser:(MWPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index;
@end

View File

@@ -22,7 +22,45 @@
#define PAGE_INDEX_TAG_OFFSET 1000 #define PAGE_INDEX_TAG_OFFSET 1000
#define PAGE_INDEX(page) ([(page) tag] - PAGE_INDEX_TAG_OFFSET) #define PAGE_INDEX(page) ([(page) tag] - PAGE_INDEX_TAG_OFFSET)
@interface MWPhotoBrowser () // Private
@interface MWPhotoBrowser () {
// Data
id <MWPhotoBrowserDelegate> _delegate;
NSUInteger _photoCount;
NSMutableArray *_photos;
NSArray *_depreciatedPhotoData; // Depreciated
// Views
UIScrollView *_pagingScrollView;
// Paging
NSMutableSet *_visiblePages, *_recycledPages;
NSUInteger _currentPageIndex;
NSUInteger _pageIndexBeforeRotation;
// Navigation & controls
UIToolbar *_toolbar;
NSTimer *_controlVisibilityTimer;
UIBarButtonItem *_previousButton, *_nextButton, *_actionButton;
UIActionSheet *_actionsSheet;
MBProgressHUD *_progressHUD;
// Appearance
UIImage *_navigationBarBackgroundImageDefault,
*_navigationBarBackgroundImageLandscapePhone;
UIColor *_previousNavBarTintColor;
UIBarStyle _previousNavBarStyle;
UIStatusBarStyle _previousStatusBarStyle;
// Misc
BOOL _displayActionButton;
BOOL _performingLayout;
BOOL _rotating;
BOOL _viewIsActive; // active as in it's in the view heirarchy
BOOL _didSavePreviousStateOfNavBar;
}
// Private Properties // Private Properties
@property (nonatomic, retain) UIColor *previousNavBarTintColor; @property (nonatomic, retain) UIColor *previousNavBarTintColor;
@@ -44,7 +82,7 @@
- (void)tilePages; - (void)tilePages;
- (BOOL)isDisplayingPageForIndex:(NSUInteger)index; - (BOOL)isDisplayingPageForIndex:(NSUInteger)index;
- (MWZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index; - (MWZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index;
- (MWZoomingScrollView *)pageDisplayingPhoto:(MWPhoto *)photo; - (MWZoomingScrollView *)pageDisplayingPhoto:(id<MWPhoto>)photo;
- (MWZoomingScrollView *)dequeueRecycledPage; - (MWZoomingScrollView *)dequeueRecycledPage;
- (void)configurePage:(MWZoomingScrollView *)page forIndex:(NSUInteger)index; - (void)configurePage:(MWZoomingScrollView *)page forIndex:(NSUInteger)index;
- (void)didStartViewingPageAtIndex:(NSUInteger)index; - (void)didStartViewingPageAtIndex:(NSUInteger)index;
@@ -72,9 +110,9 @@
// Data // Data
- (NSUInteger)numberOfPhotos; - (NSUInteger)numberOfPhotos;
- (MWPhoto *)photoAtIndex:(NSUInteger)index; - (id<MWPhoto>)photoAtIndex:(NSUInteger)index;
- (UIImage *)imageForPhoto:(MWPhoto *)photo; - (UIImage *)imageForPhoto:(id<MWPhoto>)photo;
- (void)loadAdjacentPhotosIfNecessary:(MWPhoto *)photo; - (void)loadAdjacentPhotosIfNecessary:(id<MWPhoto>)photo;
- (void)releaseAllUnderlyingPhotos; - (void)releaseAllUnderlyingPhotos;
// Actions // Actions
@@ -92,7 +130,7 @@
// MWPhotoBrowser // MWPhotoBrowser
@implementation MWPhotoBrowser @implementation MWPhotoBrowser
@synthesize delegate = _delegate; // Properties
@synthesize previousNavBarTintColor = _previousNavBarTintColor; @synthesize previousNavBarTintColor = _previousNavBarTintColor;
@synthesize navigationBarBackgroundImageDefault = _navigationBarBackgroundImageDefault, @synthesize navigationBarBackgroundImageDefault = _navigationBarBackgroundImageDefault,
navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandscapePhone; navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandscapePhone;
@@ -115,17 +153,22 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
_visiblePages = [[NSMutableSet alloc] init]; _visiblePages = [[NSMutableSet alloc] init];
_recycledPages = [[NSMutableSet alloc] init]; _recycledPages = [[NSMutableSet alloc] init];
_photos = [[NSMutableArray alloc] init]; _photos = [[NSMutableArray alloc] init];
_loadAdjacentWhenCurrentPhotoHasLoaded = NO;
_displayActionButton = NO; _displayActionButton = NO;
_didSavePreviousStateOfNavBar = NO; _didSavePreviousStateOfNavBar = NO;
// Listen for MWPhoto notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMWPhotoLoadingDidEndNotification:)
name:MWPHOTO_LOADING_DID_END_NOTIFICATION
object:nil];
} }
return self; return self;
} }
- (id)initWithDelegate:(id <MWPhotoBrowserDelegate>)delegate { - (id)initWithDelegate:(id <MWPhotoBrowserDelegate>)delegate {
if ((self = [self init])) { if ((self = [self init])) {
self.delegate = delegate; _delegate = delegate;
} }
return self; return self;
} }
@@ -138,6 +181,7 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
} }
- (void)dealloc { - (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_previousNavBarTintColor release]; [_previousNavBarTintColor release];
[_navigationBarBackgroundImageDefault release]; [_navigationBarBackgroundImageDefault release];
[_navigationBarBackgroundImageLandscapePhone release]; [_navigationBarBackgroundImageLandscapePhone release];
@@ -157,7 +201,7 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
} }
- (void)releaseAllUnderlyingPhotos { - (void)releaseAllUnderlyingPhotos {
for (id p in _photos) { if (p != [NSNull null]) [p releasePhoto]; } // Release photos for (id p in _photos) { if (p != [NSNull null]) [p unloadUnderlyingImage]; } // Release photos
} }
- (void)didReceiveMemoryWarning { - (void)didReceiveMemoryWarning {
@@ -476,8 +520,8 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
return _photoCount; return _photoCount;
} }
- (MWPhoto *)photoAtIndex:(NSUInteger)index { - (id<MWPhoto>)photoAtIndex:(NSUInteger)index {
MWPhoto *photo = nil; id <MWPhoto> photo = nil;
if (index < _photos.count) { if (index < _photos.count) {
if ([_photos objectAtIndex:index] == [NSNull null]) { if ([_photos objectAtIndex:index] == [NSNull null]) {
if ([_delegate respondsToSelector:@selector(photoBrowser:photoAtIndex:)]) { if ([_delegate respondsToSelector:@selector(photoBrowser:photoAtIndex:)]) {
@@ -498,26 +542,28 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
if ([_delegate respondsToSelector:@selector(photoBrowser:captionViewForPhotoAtIndex:)]) { if ([_delegate respondsToSelector:@selector(photoBrowser:captionViewForPhotoAtIndex:)]) {
captionView = [_delegate photoBrowser:self captionViewForPhotoAtIndex:index]; captionView = [_delegate photoBrowser:self captionViewForPhotoAtIndex:index];
} else { } else {
MWPhoto *photo = [self photoAtIndex:index]; id <MWPhoto> photo = [self photoAtIndex:index];
if (photo.caption) captionView = [[[MWCaptionView alloc] initWithPhoto:photo] autorelease]; if ([photo respondsToSelector:@selector(caption)]) {
if ([photo caption]) captionView = [[[MWCaptionView alloc] initWithPhoto:photo] autorelease];
}
} }
captionView.alpha = [self areControlsHidden] ? 0 : 1; // Initial alpha captionView.alpha = [self areControlsHidden] ? 0 : 1; // Initial alpha
return captionView; return captionView;
} }
- (UIImage *)imageForPhoto:(MWPhoto *)photo { - (UIImage *)imageForPhoto:(id<MWPhoto>)photo {
if (photo) { if (photo) {
// Get image or obtain in background // Get image or obtain in background
if ([photo isImageAvailable]) { if ([photo underlyingImage]) {
return [photo image]; return [photo underlyingImage];
} else { } else {
[photo loadImageAndNotify:self]; [photo loadUnderlyingImageAndNotify];
} }
} }
return nil; return nil;
} }
- (void)loadAdjacentPhotosIfNecessary:(MWPhoto *)photo { - (void)loadAdjacentPhotosIfNecessary:(id<MWPhoto>)photo {
MWZoomingScrollView *page = [self pageDisplayingPhoto:photo]; MWZoomingScrollView *page = [self pageDisplayingPhoto:photo];
if (page) { if (page) {
// If page is current page then initiate loading of previous and next pages // If page is current page then initiate loading of previous and next pages
@@ -525,17 +571,17 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
if (_currentPageIndex == pageIndex) { if (_currentPageIndex == pageIndex) {
if (pageIndex > 0) { if (pageIndex > 0) {
// Preload index - 1 // Preload index - 1
MWPhoto *photo = [self photoAtIndex:pageIndex-1]; id <MWPhoto> photo = [self photoAtIndex:pageIndex-1];
if (![photo isImageAvailable]) { if (![photo underlyingImage]) {
[photo loadImageAndNotify:self]; [photo loadUnderlyingImageAndNotify];
MWLog(@"Pre-loading image at index %i", pageIndex-1); MWLog(@"Pre-loading image at index %i", pageIndex-1);
} }
} }
if (pageIndex < [self numberOfPhotos] - 1) { if (pageIndex < [self numberOfPhotos] - 1) {
// Preload index + 1 // Preload index + 1
MWPhoto *photo = [self photoAtIndex:pageIndex+1]; id <MWPhoto> photo = [self photoAtIndex:pageIndex+1];
if (![photo isImageAvailable]) { if (![photo underlyingImage]) {
[photo loadImageAndNotify:self]; [photo loadUnderlyingImageAndNotify];
MWLog(@"Pre-loading image at index %i", pageIndex+1); MWLog(@"Pre-loading image at index %i", pageIndex+1);
} }
} }
@@ -543,29 +589,23 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
} }
} }
#pragma mark - MWPhotoDelegate #pragma mark - MWPhoto Loading Notification
- (void)photoDidFinishLoading:(MWPhoto *)photo { - (void)handleMWPhotoLoadingDidEndNotification:(NSNotification *)notification {
if (photo) { id <MWPhoto> photo = [notification object];
MWZoomingScrollView *page = [self pageDisplayingPhoto:photo]; MWZoomingScrollView *page = [self pageDisplayingPhoto:photo];
if (page) { if (page) {
if ([photo underlyingImage]) {
// Successful load
[page displayImage]; [page displayImage];
if (_loadAdjacentWhenCurrentPhotoHasLoaded) { [self loadAdjacentPhotosIfNecessary:photo];
[self loadAdjacentPhotosIfNecessary:photo]; } else {
_loadAdjacentWhenCurrentPhotoHasLoaded = NO; // Failed to load
} [page displayImageFailure];
} }
} }
} }
- (void)photoDidFailToLoad:(MWPhoto *)photo {
_loadAdjacentWhenCurrentPhotoHasLoaded = NO;
if (photo) {
MWZoomingScrollView *page = [self pageDisplayingPhoto:photo];
if (page) [page displayImageFailure];
}
}
#pragma mark - Paging #pragma mark - Paging
- (void)tilePages { - (void)tilePages {
@@ -637,7 +677,7 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
return thePage; return thePage;
} }
- (MWZoomingScrollView *)pageDisplayingPhoto:(MWPhoto *)photo { - (MWZoomingScrollView *)pageDisplayingPhoto:(id<MWPhoto>)photo {
MWZoomingScrollView *thePage = nil; MWZoomingScrollView *thePage = nil;
for (MWZoomingScrollView *page in _visiblePages) { for (MWZoomingScrollView *page in _visiblePages) {
if (page.photo == photo) { if (page.photo == photo) {
@@ -672,7 +712,7 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
for (i = 0; i < index-1; i++) { for (i = 0; i < index-1; i++) {
id photo = [_photos objectAtIndex:i]; id photo = [_photos objectAtIndex:i];
if (photo != [NSNull null]) { if (photo != [NSNull null]) {
[photo releasePhoto]; [photo unloadUnderlyingImage];
[_photos replaceObjectAtIndex:i withObject:[NSNull null]]; [_photos replaceObjectAtIndex:i withObject:[NSNull null]];
MWLog(@"Released underlying image at index %i", i); MWLog(@"Released underlying image at index %i", i);
} }
@@ -683,22 +723,19 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
for (i = index + 2; i < _photos.count; i++) { for (i = index + 2; i < _photos.count; i++) {
id photo = [_photos objectAtIndex:i]; id photo = [_photos objectAtIndex:i];
if (photo != [NSNull null]) { if (photo != [NSNull null]) {
[photo releasePhoto]; [photo unloadUnderlyingImage];
[_photos replaceObjectAtIndex:i withObject:[NSNull null]]; [_photos replaceObjectAtIndex:i withObject:[NSNull null]];
MWLog(@"Released underlying image at index %i", i); MWLog(@"Released underlying image at index %i", i);
} }
} }
} }
// Load adjacent images if needed // Load adjacent images if needed and the photo is already
_loadAdjacentWhenCurrentPhotoHasLoaded = NO; // loaded. Also called after photo has been loaded in background
MWPhoto *currentPhoto = [self photoAtIndex:index]; id <MWPhoto> currentPhoto = [self photoAtIndex:index];
if ([currentPhoto isImageAvailable]) { if ([currentPhoto underlyingImage]) {
// photo loaded so load ajacent now // photo loaded so load ajacent now
[self loadAdjacentPhotosIfNecessary:currentPhoto]; [self loadAdjacentPhotosIfNecessary:currentPhoto];
} else {
// Photo not loaded so load adjacent when it is
_loadAdjacentWhenCurrentPhotoHasLoaded = YES;
} }
} }
@@ -919,8 +956,8 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
// Dismiss // Dismiss
[_actionsSheet dismissWithClickedButtonIndex:_actionsSheet.cancelButtonIndex animated:YES]; [_actionsSheet dismissWithClickedButtonIndex:_actionsSheet.cancelButtonIndex animated:YES];
} else { } else {
MWPhoto *photo = [self photoAtIndex:_currentPageIndex]; id <MWPhoto> photo = [self photoAtIndex:_currentPageIndex];
if ([self numberOfPhotos] > 0 && [photo isImageAvailable]) { if ([self numberOfPhotos] > 0 && [photo underlyingImage]) {
// Keep controls hidden // Keep controls hidden
[self setControlsHidden:NO animated:YES permanent:YES]; [self setControlsHidden:NO animated:YES permanent:YES];
@@ -1006,16 +1043,16 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
#pragma mark - Actions #pragma mark - Actions
- (void)savePhoto { - (void)savePhoto {
MWPhoto *photo = [self photoAtIndex:_currentPageIndex]; id <MWPhoto> photo = [self photoAtIndex:_currentPageIndex];
if ([photo isImageAvailable]) { if ([photo underlyingImage]) {
[self showProgressHUDWithMessage:@"Saving..."]; [self showProgressHUDWithMessage:@"Saving..."];
[self performSelector:@selector(actuallySavePhoto:) withObject:photo afterDelay:0]; [self performSelector:@selector(actuallySavePhoto:) withObject:photo afterDelay:0];
} }
} }
- (void)actuallySavePhoto:(MWPhoto *)photo { - (void)actuallySavePhoto:(id<MWPhoto>)photo {
if ([photo isImageAvailable]) { if ([photo underlyingImage]) {
UIImageWriteToSavedPhotosAlbum(photo.image, self, UIImageWriteToSavedPhotosAlbum([photo underlyingImage], self,
@selector(image:didFinishSavingWithError:contextInfo:), nil); @selector(image:didFinishSavingWithError:contextInfo:), nil);
} }
} }
@@ -1026,16 +1063,16 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
} }
- (void)copyPhoto { - (void)copyPhoto {
MWPhoto *photo = [self photoAtIndex:_currentPageIndex]; id <MWPhoto> photo = [self photoAtIndex:_currentPageIndex];
if ([photo isImageAvailable]) { if ([photo underlyingImage]) {
[self showProgressHUDWithMessage:@"Copying..."]; [self showProgressHUDWithMessage:@"Copying..."];
[self performSelector:@selector(actuallyCopyPhoto:) withObject:photo afterDelay:0]; [self performSelector:@selector(actuallyCopyPhoto:) withObject:photo afterDelay:0];
} }
} }
- (void)actuallyCopyPhoto:(MWPhoto *)photo { - (void)actuallyCopyPhoto:(id<MWPhoto>)photo {
if ([photo isImageAvailable]) { if ([photo underlyingImage]) {
[[UIPasteboard generalPasteboard] setData:UIImagePNGRepresentation(photo.image) [[UIPasteboard generalPasteboard] setData:UIImagePNGRepresentation([photo underlyingImage])
forPasteboardType:@"public.png"]; forPasteboardType:@"public.png"];
[self showProgressHUDCompleteMessage:@"Copied"]; [self showProgressHUDCompleteMessage:@"Copied"];
[self hideControlsAfterDelay]; // Continue as normal... [self hideControlsAfterDelay]; // Continue as normal...
@@ -1043,19 +1080,19 @@ navigationBarBackgroundImageLandscapePhone = _navigationBarBackgroundImageLandsc
} }
- (void)emailPhoto { - (void)emailPhoto {
MWPhoto *photo = [self photoAtIndex:_currentPageIndex]; id <MWPhoto> photo = [self photoAtIndex:_currentPageIndex];
if ([photo isImageAvailable]) { if ([photo underlyingImage]) {
[self showProgressHUDWithMessage:@"Preparing..."]; [self showProgressHUDWithMessage:@"Preparing..."];
[self performSelector:@selector(actuallyEmailPhoto:) withObject:photo afterDelay:0]; [self performSelector:@selector(actuallyEmailPhoto:) withObject:photo afterDelay:0];
} }
} }
- (void)actuallyEmailPhoto:(MWPhoto *)photo { - (void)actuallyEmailPhoto:(id<MWPhoto>)photo {
if ([photo isImageAvailable]) { if ([photo underlyingImage]) {
MFMailComposeViewController *emailer = [[MFMailComposeViewController alloc] init]; MFMailComposeViewController *emailer = [[MFMailComposeViewController alloc] init];
emailer.mailComposeDelegate = self; emailer.mailComposeDelegate = self;
[emailer setSubject:@"Photo"]; [emailer setSubject:@"Photo"];
[emailer addAttachmentData:UIImagePNGRepresentation(photo.image) mimeType:@"png" fileName:@"Photo.png"]; [emailer addAttachmentData:UIImagePNGRepresentation([photo underlyingImage]) mimeType:@"png" fileName:@"Photo.png"];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
emailer.modalPresentationStyle = UIModalPresentationPageSheet; emailer.modalPresentationStyle = UIModalPresentationPageSheet;
} }

60
Classes/MWPhotoProtocol.h Normal file
View File

@@ -0,0 +1,60 @@
//
// MWPhotoProtocol.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 02/01/2012.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
// Name of notification used when a photo has completed loading process
// Used to notify browser display the image
#define MWPHOTO_LOADING_DID_END_NOTIFICATION @"MWPHOTO_LOADING_DID_END_NOTIFICATION"
// If you wish to use your own data models for photo then they must conform
// to this protocol. See instructions for details on each method.
// Otherwise you can use the MWPhoto object or subclass it yourself to
// store more information per photo.
//
// You can see the MWPhoto class for an example implementation of this protocol
//
@protocol MWPhoto <NSObject>
@required
// Return underlying UIImage to be displayed
// Return nil if the image is not immediately available (loaded into memory, preferably
// already decompressed) and needs to be loaded from a source (cache, file, web, etc)
// IMPORTANT: You should *NOT* use this method to initiate
// fetching of images from any external of source. That should be handled
// in -loadUnderlyingImageAndNotify: which may be called by the photo browser if this
// methods returns nil.
- (UIImage *)underlyingImage;
// Called when the browser has determined the underlying images is not
// already loaded into memory but needs it.
// You must load the image asyncronously (and decompress it for better performance).
// It is recommended that you use SDWebImageDecoder to perform the decompression.
// See MWPhoto object for an example implementation.
// When the underlying UIImage is loaded (or failed to load) you should post the following
// notification:
//
// [[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_LOADING_DID_END_NOTIFICATION
// object:self];
//
- (void)loadUnderlyingImageAndNotify;
// This is called when the photo browser has determined the photo data
// is no longer needed or there are low memory conditions
// You should release any underlying (possibly large and decompressed) image data
// as long as the image can be re-loaded (from cache, file, or URL)
- (void)unloadUnderlyingImage;
@optional
// Return a caption string to be displayed over the image
// Return nil to display no caption
- (NSString *)caption;
@end

View File

@@ -7,6 +7,7 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "MWPhotoProtocol.h"
#import "MWTapDetectingImageView.h" #import "MWTapDetectingImageView.h"
#import "MWTapDetectingView.h" #import "MWTapDetectingView.h"
@@ -15,7 +16,7 @@
@interface MWZoomingScrollView : UIScrollView <UIScrollViewDelegate, MWTapDetectingImageViewDelegate, MWTapDetectingViewDelegate> { @interface MWZoomingScrollView : UIScrollView <UIScrollViewDelegate, MWTapDetectingImageViewDelegate, MWTapDetectingViewDelegate> {
MWPhotoBrowser *_photoBrowser; MWPhotoBrowser *_photoBrowser;
MWPhoto *_photo; id<MWPhoto> _photo;
// This view references the related caption view for simplified // This view references the related caption view for simplified
// handling in photo browser // handling in photo browser
@@ -28,7 +29,7 @@
} }
@property (nonatomic, retain) MWCaptionView *captionView; @property (nonatomic, retain) MWCaptionView *captionView;
@property (nonatomic, retain) MWPhoto *photo; @property (nonatomic, retain) id<MWPhoto> photo;
- (id)initWithPhotoBrowser:(MWPhotoBrowser *)browser; - (id)initWithPhotoBrowser:(MWPhotoBrowser *)browser;
- (void)displayImage; - (void)displayImage;

View File

@@ -12,7 +12,7 @@
// Declare private methods of browser // Declare private methods of browser
@interface MWPhotoBrowser () @interface MWPhotoBrowser ()
- (UIImage *)imageForPhoto:(MWPhoto *)photo; - (UIImage *)imageForPhoto:(id<MWPhoto>)photo;
- (void)cancelControlHiding; - (void)cancelControlHiding;
- (void)hideControlsAfterDelay; - (void)hideControlsAfterDelay;
@end @end
@@ -75,10 +75,9 @@
[super dealloc]; [super dealloc];
} }
- (void)setPhoto:(MWPhoto *)photo { - (void)setPhoto:(id<MWPhoto>)photo {
_photoImageView.image = nil; // Release image _photoImageView.image = nil; // Release image
if (_photo != photo) { if (_photo != photo) {
[_photo setPhotoLoadingDelegate:nil];
[_photo release]; [_photo release];
_photo = [photo retain]; _photo = [photo retain];
} }
@@ -86,7 +85,6 @@
} }
- (void)prepareForReuse { - (void)prepareForReuse {
self.photo.photoLoadingDelegate = nil;
self.photo = nil; self.photo = nil;
[_captionView removeFromSuperview]; [_captionView removeFromSuperview];
self.captionView = nil; self.captionView = nil;

View File

@@ -152,14 +152,14 @@
return _photos.count; return _photos.count;
} }
- (MWPhoto *)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index { - (id<MWPhoto>)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index {
if (index < _photos.count) if (index < _photos.count)
return [_photos objectAtIndex:index]; return [_photos objectAtIndex:index];
return nil; return nil;
} }
//- (MWCaptionView *)photoBrowser:(MWPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index { //- (MWCaptionView *)photoBrowser:(MWPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index {
// MWPhoto *photo = [self.photos objectAtIndex:index]; // (id<MWPhoto>)photo = [self.photos objectAtIndex:index];
// MWCaptionView *captionView = [[MWCaptionView alloc] initWithPhoto:photo]; // MWCaptionView *captionView = [[MWCaptionView alloc] initWithPhoto:photo];
// return [captionView autorelease]; // return [captionView autorelease];
//} //}

View File

@@ -54,6 +54,7 @@
28D7ACF70DDB3853001CB0EB /* MWPhotoBrowser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWPhotoBrowser.m; sourceTree = "<group>"; }; 28D7ACF70DDB3853001CB0EB /* MWPhotoBrowser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWPhotoBrowser.m; sourceTree = "<group>"; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
32CA4F630368D1EE00C91783 /* MWPhotoBrowser_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWPhotoBrowser_Prefix.pch; sourceTree = "<group>"; }; 32CA4F630368D1EE00C91783 /* MWPhotoBrowser_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWPhotoBrowser_Prefix.pch; sourceTree = "<group>"; };
4C0E5E2E14B1CB6D0076F4A9 /* MWPhotoProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWPhotoProtocol.h; sourceTree = "<group>"; };
4C15BF5014AE300F004F9CA6 /* MWCaptionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWCaptionView.h; sourceTree = "<group>"; }; 4C15BF5014AE300F004F9CA6 /* MWCaptionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MWCaptionView.h; sourceTree = "<group>"; };
4C15BF5114AE300F004F9CA6 /* MWCaptionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWCaptionView.m; sourceTree = "<group>"; }; 4C15BF5114AE300F004F9CA6 /* MWCaptionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MWCaptionView.m; sourceTree = "<group>"; };
4C15BF5714AE5358004F9CA6 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; }; 4C15BF5714AE5358004F9CA6 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
@@ -255,6 +256,7 @@
28D7ACF70DDB3853001CB0EB /* MWPhotoBrowser.m */, 28D7ACF70DDB3853001CB0EB /* MWPhotoBrowser.m */,
4C75D46D126B659A004D0ECF /* MWPhoto.h */, 4C75D46D126B659A004D0ECF /* MWPhoto.h */,
4C75D46E126B659A004D0ECF /* MWPhoto.m */, 4C75D46E126B659A004D0ECF /* MWPhoto.m */,
4C0E5E2E14B1CB6D0076F4A9 /* MWPhotoProtocol.h */,
4C15BF5014AE300F004F9CA6 /* MWCaptionView.h */, 4C15BF5014AE300F004F9CA6 /* MWCaptionView.h */,
4C15BF5114AE300F004F9CA6 /* MWCaptionView.m */, 4C15BF5114AE300F004F9CA6 /* MWCaptionView.m */,
4CA3556814AF24A3009D09B3 /* Other Subclasses */, 4CA3556814AF24A3009D09B3 /* Other Subclasses */,
@@ -381,7 +383,6 @@
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = MWPhotoBrowser_Prefix.pch; GCC_PREFIX_HEADER = MWPhotoBrowser_Prefix.pch;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
INFOPLIST_FILE = "MWPhotoBrowser-Info.plist"; INFOPLIST_FILE = "MWPhotoBrowser-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 3.0; IPHONEOS_DEPLOYMENT_TARGET = 3.0;
PRODUCT_NAME = MWPhotoBrowser; PRODUCT_NAME = MWPhotoBrowser;
@@ -396,7 +397,6 @@
COPY_PHASE_STRIP = YES; COPY_PHASE_STRIP = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = MWPhotoBrowser_Prefix.pch; GCC_PREFIX_HEADER = MWPhotoBrowser_Prefix.pch;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
INFOPLIST_FILE = "MWPhotoBrowser-Info.plist"; INFOPLIST_FILE = "MWPhotoBrowser-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 3.0; IPHONEOS_DEPLOYMENT_TARGET = 3.0;
PRODUCT_NAME = MWPhotoBrowser; PRODUCT_NAME = MWPhotoBrowser;
@@ -409,7 +409,7 @@
buildSettings = { buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)"; ARCHS = "$(ARCHS_STANDARD_32_BIT)";
GCC_C_LANGUAGE_STANDARD = c99; GCC_C_LANGUAGE_STANDARD = c99;
GCC_VERSION = com.apple.compilers.llvmgcc42; GCC_VERSION = "";
GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -421,7 +421,7 @@
buildSettings = { buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_BIT)"; ARCHS = "$(ARCHS_STANDARD_32_BIT)";
GCC_C_LANGUAGE_STANDARD = c99; GCC_C_LANGUAGE_STANDARD = c99;
GCC_VERSION = com.apple.compilers.llvmgcc42; GCC_VERSION = "";
GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";

View File

@@ -15,9 +15,9 @@ MWPhotoBrowser is an implementation of a photo browser similar to the native Pho
## Usage ## Usage
MWPhotoBrowser is designed to be presented within a navigation controller. Simply set the delegate (which must conform to `MWPhotoBrowserDelegate`) and implement the 2 required delegate methods to provide the photo browser with the data in the form of `MWPhoto` objects. MWPhotoBrowser is designed to be presented within a navigation controller. Simply set the delegate (which must conform to `MWPhotoBrowserDelegate`) and implement the 2 required delegate methods to provide the photo browser with the data in the form of `MWPhoto` objects. You can create an `MWPhoto` object by providing a `UIImage` object, a file path to a physical image file, or a URL to an image online.
You can create an `MWPhoto` object by providing a `UIImage` object, a file path to a physical image file, or a URL to an image online. `MWPhoto` objects handle caching, file management, downloading of web images, and various optimisations for you. If however you would like to use your own data model to represent photos you can simply ensure your model conforms to the `MWPhoto` protocol. You can then handle the management of caching, downloads, etc, yourself. More information on this can be found in `MWPhotoProtocol.h`.
See the code snippet below for an example of how to implement the photo browser. There is also a simple demo app within the project. See the code snippet below for an example of how to implement the photo browser. There is also a simple demo app within the project.