mirror of
https://github.com/zhigang1992/MWPhotoBrowser.git
synced 2026-04-30 21:21:59 +08:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
60
Classes/MWPhotoProtocol.h
Normal 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
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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];
|
||||||
//}
|
//}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user