add 图片浏览

This commit is contained in:
wu736139669
2015-04-02 09:37:44 +08:00
parent 48e33aec03
commit c42f1f5030
125 changed files with 12910 additions and 2784 deletions
@@ -0,0 +1,33 @@
//
// MWCaptionView.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 30/12/2011.
// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "MWPhotoProtocol.h"
@interface MWCaptionView : UIToolbar
// Init
- (id)initWithPhoto:(id<MWPhoto>)photo;
// To create your own custom caption view, subclass this view
// and override the following two methods (as well as any other
// UIView methods that you see fit):
// Override -setupCaption so setup your subviews and customise the appearance
// of your custom caption
// You can access the photo's data by accessing the _photo ivar
// If you need more data per photo then simply subclass MWPhoto and return your
// subclass to the photo browsers -photoBrowser:photoAtIndex: delegate method
- (void)setupCaption;
// Override -sizeThatFits: and return a CGSize specifying the height of your
// custom caption view. With width property is ignored and the caption is displayed
// the full width of the screen
- (CGSize)sizeThatFits:(CGSize)size;
@end
+106
View File
@@ -0,0 +1,106 @@
//
// MWCaptionView.m
// MWPhotoBrowser
//
// Created by Michael Waterfall on 30/12/2011.
// Copyright (c) 2011 __MyCompanyName__. All rights reserved.
//
#import "MWCommon.h"
#import "MWCaptionView.h"
#import "MWPhoto.h"
static const CGFloat labelPadding = 10;
// Private
@interface MWCaptionView () {
id <MWPhoto> _photo;
UILabel *_label;
}
@end
@implementation MWCaptionView
- (id)initWithPhoto:(id<MWPhoto>)photo {
self = [super initWithFrame:CGRectMake(0, 0, 320, 44)]; // Random initial frame
if (self) {
self.userInteractionEnabled = NO;
_photo = photo;
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7")) {
// Use iOS 7 blurry goodness
self.barStyle = UIBarStyleBlackTranslucent;
self.tintColor = nil;
self.barTintColor = nil;
self.barStyle = UIBarStyleBlackTranslucent;
[self setBackgroundImage:nil forToolbarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault];
} else {
// Transparent black with no gloss
CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [[UIColor colorWithWhite:0 alpha:0.6] CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self setBackgroundImage:image forToolbarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault];
}
self.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin;
[self setupCaption];
}
return self;
}
- (CGSize)sizeThatFits:(CGSize)size {
CGFloat maxHeight = 9999;
if (_label.numberOfLines > 0) maxHeight = _label.font.leading*_label.numberOfLines;
CGSize textSize;
if ([NSString instancesRespondToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) {
textSize = [_label.text boundingRectWithSize:CGSizeMake(size.width - labelPadding*2, maxHeight)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:_label.font}
context:nil].size;
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
textSize = [_label.text sizeWithFont:_label.font
constrainedToSize:CGSizeMake(size.width - labelPadding*2, maxHeight)
lineBreakMode:_label.lineBreakMode];
#pragma clang diagnostic pop
}
return CGSizeMake(size.width, textSize.height + labelPadding * 2);
}
- (void)setupCaption {
_label = [[UILabel alloc] initWithFrame:CGRectIntegral(CGRectMake(labelPadding, 0,
self.bounds.size.width-labelPadding*2,
self.bounds.size.height))];
_label.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
_label.opaque = NO;
_label.backgroundColor = [UIColor clearColor];
if (SYSTEM_VERSION_LESS_THAN(@"6")) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_label.textAlignment = UITextAlignmentCenter;
_label.lineBreakMode = UILineBreakModeWordWrap;
#pragma clang diagnostic pop
} else {
_label.textAlignment = NSTextAlignmentCenter;
_label.lineBreakMode = NSLineBreakByWordWrapping;
}
_label.numberOfLines = 0;
_label.textColor = [UIColor whiteColor];
if (SYSTEM_VERSION_LESS_THAN(@"7")) {
// Shadow on 6 and below
_label.shadowColor = [UIColor blackColor];
_label.shadowOffset = CGSizeMake(1, 1);
}
_label.font = [UIFont systemFontOfSize:17];
if ([_photo respondsToSelector:@selector(caption)]) {
_label.text = [_photo caption] ? [_photo caption] : @" ";
}
[self addSubview:_label];
}
@end
+12
View File
@@ -0,0 +1,12 @@
//
// MWPreprocessor.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 01/10/2013.
//
#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
+24
View File
@@ -0,0 +1,24 @@
//
// MWGridCell.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 08/10/2013.
//
//
#import <UIKit/UIKit.h>
#import "MWPhoto.h"
#import "MWGridViewController.h"
#import "PSTCollectionView.h"
@interface MWGridCell : PSTCollectionViewCell {}
@property (nonatomic, weak) MWGridViewController *gridController;
@property (nonatomic) NSUInteger index;
@property (nonatomic) id <MWPhoto> photo;
@property (nonatomic) BOOL selectionMode;
@property (nonatomic) BOOL isSelected;
- (void)displayImage;
@end
+223
View File
@@ -0,0 +1,223 @@
//
// MWGridCell.m
// MWPhotoBrowser
//
// Created by Michael Waterfall on 08/10/2013.
//
//
#import "MWGridCell.h"
#import "MWCommon.h"
#import "MWPhotoBrowserPrivate.h"
#import "DACircularProgressView.h"
@interface MWGridCell () {
UIImageView *_imageView;
UIImageView *_loadingError;
DACircularProgressView *_loadingIndicator;
UIButton *_selectedButton;
}
@end
@implementation MWGridCell
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Grey background
self.backgroundColor = [UIColor colorWithWhite:0.12 alpha:1];
// Image
_imageView = [UIImageView new];
_imageView.frame = self.bounds;
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.clipsToBounds = YES;
_imageView.autoresizesSubviews = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self addSubview:_imageView];
// Selection button
_selectedButton = [UIButton buttonWithType:UIButtonTypeCustom];
_selectedButton.contentMode = UIViewContentModeTopRight;
_selectedButton.adjustsImageWhenHighlighted = NO;
[_selectedButton setImage:nil forState:UIControlStateNormal];
[_selectedButton setImage:[UIImage imageNamed:@"MWPhotoBrowser.bundle/images/ImageSelectedSmallOff.png"] forState:UIControlStateNormal];
[_selectedButton setImage:[UIImage imageNamed:@"MWPhotoBrowser.bundle/images/ImageSelectedSmallOn.png"] forState:UIControlStateSelected];
[_selectedButton addTarget:self action:@selector(selectionButtonPressed) forControlEvents:UIControlEventTouchDown];
_selectedButton.hidden = YES;
_selectedButton.frame = CGRectMake(0, 0, 44, 44);
[self addSubview:_selectedButton];
// Loading indicator
_loadingIndicator = [[DACircularProgressView alloc] initWithFrame:CGRectMake(0, 0, 40.0f, 40.0f)];
_loadingIndicator.userInteractionEnabled = NO;
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7")) {
_loadingIndicator.thicknessRatio = 0.1;
_loadingIndicator.roundedCorners = NO;
} else {
_loadingIndicator.thicknessRatio = 0.2;
_loadingIndicator.roundedCorners = YES;
}
[self addSubview:_loadingIndicator];
// Listen for photo loading notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(setProgressFromNotification:)
name:MWPHOTO_PROGRESS_NOTIFICATION
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMWPhotoLoadingDidEndNotification:)
name:MWPHOTO_LOADING_DID_END_NOTIFICATION
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - View
- (void)layoutSubviews {
[super layoutSubviews];
_imageView.frame = self.bounds;
_loadingIndicator.frame = CGRectMake(floorf((self.bounds.size.width - _loadingIndicator.frame.size.width) / 2.),
floorf((self.bounds.size.height - _loadingIndicator.frame.size.height) / 2),
_loadingIndicator.frame.size.width,
_loadingIndicator.frame.size.height);
_selectedButton.frame = CGRectMake(self.bounds.size.width - _selectedButton.frame.size.width - 0,
0, _selectedButton.frame.size.width, _selectedButton.frame.size.height);
}
#pragma mark - Cell
- (void)prepareForReuse {
_photo = nil;
_gridController = nil;
_imageView.image = nil;
_loadingIndicator.progress = 0;
_selectedButton.hidden = YES;
[self hideImageFailure];
[super prepareForReuse];
}
#pragma mark - Image Handling
- (void)setPhoto:(id <MWPhoto>)photo {
_photo = photo;
if (_photo) {
if (![_photo underlyingImage]) {
[self showLoadingIndicator];
} else {
[self hideLoadingIndicator];
}
} else {
[self showImageFailure];
}
}
- (void)displayImage {
_imageView.image = [_photo underlyingImage];
_selectedButton.hidden = !_selectionMode;
[self hideImageFailure];
}
#pragma mark - Selection
- (void)setSelectionMode:(BOOL)selectionMode {
_selectionMode = selectionMode;
}
- (void)setIsSelected:(BOOL)isSelected {
_isSelected = isSelected;
_selectedButton.selected = isSelected;
}
- (void)selectionButtonPressed {
_selectedButton.selected = !_selectedButton.selected;
[_gridController.browser setPhotoSelected:_selectedButton.selected atIndex:_index];
}
#pragma mark - Touches
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_imageView.alpha = 0.6;
[super touchesBegan:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
_imageView.alpha = 1;
[super touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
_imageView.alpha = 1;
[super touchesCancelled:touches withEvent:event];
}
#pragma mark Indicators
- (void)hideLoadingIndicator {
_loadingIndicator.hidden = YES;
}
- (void)showLoadingIndicator {
_loadingIndicator.progress = 0;
_loadingIndicator.hidden = NO;
[self hideImageFailure];
}
- (void)showImageFailure {
if (!_loadingError) {
_loadingError = [UIImageView new];
_loadingError.image = [UIImage imageNamed:@"MWPhotoBrowser.bundle/images/ImageError.png"];
_loadingError.userInteractionEnabled = NO;
[_loadingError sizeToFit];
[self addSubview:_loadingError];
}
[self hideLoadingIndicator];
_imageView.image = nil;
_loadingError.frame = CGRectMake(floorf((self.bounds.size.width - _loadingError.frame.size.width) / 2.),
floorf((self.bounds.size.height - _loadingError.frame.size.height) / 2),
_loadingError.frame.size.width,
_loadingError.frame.size.height);
}
- (void)hideImageFailure {
if (_loadingError) {
[_loadingError removeFromSuperview];
_loadingError = nil;
}
}
#pragma mark - Notifications
- (void)setProgressFromNotification:(NSNotification *)notification {
NSDictionary *dict = [notification object];
id <MWPhoto> photoWithProgress = [dict objectForKey:@"photo"];
if (photoWithProgress == _photo) {
// NSLog(@"%f", [[dict valueForKey:@"progress"] floatValue]);
float progress = [[dict valueForKey:@"progress"] floatValue];
_loadingIndicator.progress = MAX(MIN(1, progress), 0);
}
}
- (void)handleMWPhotoLoadingDidEndNotification:(NSNotification *)notification {
id <MWPhoto> photo = [notification object];
if (photo == _photo) {
if ([photo underlyingImage]) {
// Successful load
[self displayImage];
} else {
// Failed to load
[self showImageFailure];
}
[self hideLoadingIndicator];
}
}
@end
@@ -0,0 +1,19 @@
//
// MWGridViewController.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 08/10/2013.
//
//
#import <UIKit/UIKit.h>
#import "MWPhotoBrowser.h"
#import "PSTCollectionView.h"
@interface MWGridViewController : PSTCollectionViewController {}
@property (nonatomic, assign) MWPhotoBrowser *browser;
@property (nonatomic) BOOL selectionMode;
@property (nonatomic) CGPoint initialContentOffset;
@end
@@ -0,0 +1,205 @@
//
// MWGridViewController.m
// MWPhotoBrowser
//
// Created by Michael Waterfall on 08/10/2013.
//
//
#import "MWGridViewController.h"
#import "MWGridCell.h"
#import "MWPhotoBrowserPrivate.h"
#import "MWCommon.h"
@interface MWGridViewController () {
// Store margins for current setup
CGFloat _margin, _gutter, _marginL, _gutterL, _columns, _columnsL;
}
@end
@implementation MWGridViewController
- (id)init {
if ((self = [super initWithCollectionViewLayout:[PSTCollectionViewFlowLayout new]])) {
// Defaults
_columns = 3, _columnsL = 4;
_margin = 0, _gutter = 1;
_marginL = 0, _gutterL = 1;
// For pixel perfection...
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// iPad
_columns = 6, _columnsL = 8;
_margin = 1, _gutter = 2;
_marginL = 1, _gutterL = 2;
} else if ([UIScreen mainScreen].bounds.size.height == 480) {
// iPhone 3.5 inch
_columns = 3, _columnsL = 4;
_margin = 0, _gutter = 1;
_marginL = 1, _gutterL = 2;
} else {
// iPhone 4 inch
_columns = 3, _columnsL = 5;
_margin = 0, _gutter = 1;
_marginL = 0, _gutterL = 2;
}
_initialContentOffset = CGPointMake(0, CGFLOAT_MAX);
}
return self;
}
#pragma mark - View
- (void)viewDidLoad {
[super viewDidLoad];
[self.collectionView registerClass:[MWGridCell class] forCellWithReuseIdentifier:@"GridCell"];
self.collectionView.alwaysBounceVertical = YES;
self.collectionView.backgroundColor = [UIColor blackColor];
}
- (void)viewWillDisappear:(BOOL)animated {
// Cancel outstanding loading
NSArray *visibleCells = [self.collectionView visibleCells];
if (visibleCells) {
for (MWGridCell *cell in visibleCells) {
[cell.photo cancelAnyLoading];
}
}
[super viewWillDisappear:animated];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self performLayout];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// Move to previous content offset
if (_initialContentOffset.y != CGFLOAT_MAX) {
self.collectionView.contentOffset = _initialContentOffset;
}
CGPoint currentContentOffset = self.collectionView.contentOffset;
// Get scroll position to have the current photo on screen
if (_browser.numberOfPhotos > 0) {
NSIndexPath *currentPhotoIndexPath = [NSIndexPath indexPathForItem:_browser.currentIndex inSection:0];
[self.collectionView scrollToItemAtIndexPath:currentPhotoIndexPath atScrollPosition:PSTCollectionViewScrollPositionNone animated:NO];
}
CGPoint offsetToShowCurrent = self.collectionView.contentOffset;
// Only commit to using the scrolled position if it differs from the initial content offset
if (!CGPointEqualToPoint(offsetToShowCurrent, currentContentOffset)) {
// Use offset to show current
self.collectionView.contentOffset = offsetToShowCurrent;
} else {
// Stick with initial
self.collectionView.contentOffset = currentContentOffset;
}
}
- (void)performLayout {
UINavigationBar *navBar = self.navigationController.navigationBar;
CGFloat yAdjust = 0;
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
if (SYSTEM_VERSION_LESS_THAN(@"7") && !self.browser.wantsFullScreenLayout) yAdjust = -20;
#endif
self.collectionView.contentInset = UIEdgeInsetsMake(navBar.frame.origin.y + navBar.frame.size.height + [self getGutter] + yAdjust, 0, 0, 0);
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self.collectionView reloadData];
[self performLayout]; // needed for iOS 5 & 6
}
#pragma mark - Layout
- (CGFloat)getColumns {
if ((UIInterfaceOrientationIsPortrait(self.interfaceOrientation))) {
return _columns;
} else {
return _columnsL;
}
}
- (CGFloat)getMargin {
if ((UIInterfaceOrientationIsPortrait(self.interfaceOrientation))) {
return _margin;
} else {
return _marginL;
}
}
- (CGFloat)getGutter {
if ((UIInterfaceOrientationIsPortrait(self.interfaceOrientation))) {
return _gutter;
} else {
return _gutterL;
}
}
#pragma mark - Collection View
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return [_browser numberOfPhotos];
}
- (PSTCollectionViewCell *)collectionView:(PSTCollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MWGridCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"GridCell" forIndexPath:indexPath];
if (!cell) {
cell = [[MWGridCell alloc] init];
}
id <MWPhoto> photo = [_browser thumbPhotoAtIndex:indexPath.row];
cell.photo = photo;
cell.gridController = self;
cell.selectionMode = _selectionMode;
cell.isSelected = [_browser photoIsSelectedAtIndex:indexPath.row];
cell.index = indexPath.row;
UIImage *img = [_browser imageForPhoto:photo];
if (img) {
[cell displayImage];
} else {
[photo loadUnderlyingImageAndNotify];
}
return cell;
}
- (void)collectionView:(PSTCollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[_browser setCurrentPhotoIndex:indexPath.row];
[_browser hideGrid];
}
- (void)collectionView:(PSTCollectionView *)collectionView didEndDisplayingCell:(PSTCollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
[((MWGridCell *)cell).photo cancelAnyLoading];
}
- (CGSize)collectionView:(PSTCollectionView *)collectionView layout:(PSTCollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat margin = [self getMargin];
CGFloat gutter = [self getGutter];
CGFloat columns = [self getColumns];
CGFloat value = floorf(((self.view.bounds.size.width - (columns - 1) * gutter - 2 * margin) / columns));
return CGSizeMake(value, value);
}
- (CGFloat)collectionView:(PSTCollectionView *)collectionView layout:(PSTCollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return [self getGutter];
}
- (CGFloat)collectionView:(PSTCollectionView *)collectionView layout:(PSTCollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return [self getGutter];
}
- (UIEdgeInsets)collectionView:(PSTCollectionView *)collectionView layout:(PSTCollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
CGFloat margin = [self getMargin];
return UIEdgeInsetsMake(margin, margin, margin, margin);
}
@end
+32
View File
@@ -0,0 +1,32 @@
//
// MWPhoto.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 17/10/2010.
// Copyright 2010 d3i. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MWPhotoProtocol.h"
// This class models a photo/image and it's caption
// If you want to handle photos, caching, decompression
// yourself then you can simply ensure your custom data model
// conforms to MWPhotoProtocol
@interface MWPhoto : NSObject <MWPhoto>
@property (nonatomic, strong) NSString *caption;
@property (nonatomic, readonly) UIImage *image;
@property (nonatomic, readonly) NSURL *photoURL;
@property (nonatomic, readonly) NSString *filePath __attribute__((deprecated("Use photoURL"))); // Depreciated
+ (MWPhoto *)photoWithImage:(UIImage *)image;
+ (MWPhoto *)photoWithFilePath:(NSString *)path __attribute__((deprecated("Use photoWithURL: with a file URL"))); // Depreciated
+ (MWPhoto *)photoWithURL:(NSURL *)url;
- (id)initWithImage:(UIImage *)image;
- (id)initWithURL:(NSURL *)url;
- (id)initWithFilePath:(NSString *)path __attribute__((deprecated("Use initWithURL: with a file URL"))); // Depreciated
@end
+219
View File
@@ -0,0 +1,219 @@
//
// MWPhoto.m
// MWPhotoBrowser
//
// Created by Michael Waterfall on 17/10/2010.
// Copyright 2010 d3i. All rights reserved.
//
#import "MWPhoto.h"
#import "MWPhotoBrowser.h"
#import "SDWebImageDecoder.h"
#import "SDWebImageManager.h"
#import "SDWebImageOperation.h"
#import <AssetsLibrary/AssetsLibrary.h>
@interface MWPhoto () {
BOOL _loadingInProgress;
id <SDWebImageOperation> _webImageOperation;
}
- (void)imageLoadingComplete;
@end
@implementation MWPhoto
@synthesize underlyingImage = _underlyingImage; // synth property from protocol
#pragma mark - Class Methods
+ (MWPhoto *)photoWithImage:(UIImage *)image {
return [[MWPhoto alloc] initWithImage:image];
}
// Deprecated
+ (MWPhoto *)photoWithFilePath:(NSString *)path {
return [MWPhoto photoWithURL:[NSURL fileURLWithPath:path]];
}
+ (MWPhoto *)photoWithURL:(NSURL *)url {
return [[MWPhoto alloc] initWithURL:url];
}
#pragma mark - Init
- (id)initWithImage:(UIImage *)image {
if ((self = [super init])) {
_image = image;
}
return self;
}
// Deprecated
- (id)initWithFilePath:(NSString *)path {
if ((self = [super init])) {
_photoURL = [NSURL fileURLWithPath:path];
}
return self;
}
- (id)initWithURL:(NSURL *)url {
if ((self = [super init])) {
_photoURL = [url copy];
}
return self;
}
#pragma mark - MWPhoto Protocol Methods
- (UIImage *)underlyingImage {
return _underlyingImage;
}
- (void)loadUnderlyingImageAndNotify {
NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread.");
if (_loadingInProgress) return;
_loadingInProgress = YES;
@try {
if (self.underlyingImage) {
[self imageLoadingComplete];
} else {
[self performLoadUnderlyingImageAndNotify];
}
}
@catch (NSException *exception) {
self.underlyingImage = nil;
_loadingInProgress = NO;
[self imageLoadingComplete];
}
@finally {
}
}
// Set the underlyingImage
- (void)performLoadUnderlyingImageAndNotify {
// Get underlying image
if (_image) {
// We have UIImage!
self.underlyingImage = _image;
[self imageLoadingComplete];
} else if (_photoURL) {
// Check what type of url it is
if ([[[_photoURL scheme] lowercaseString] isEqualToString:@"assets-library"]) {
// Load from asset library async
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@autoreleasepool {
@try {
ALAssetsLibrary *assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:_photoURL
resultBlock:^(ALAsset *asset){
ALAssetRepresentation *rep = [asset defaultRepresentation];
CGImageRef iref = [rep fullScreenImage];
if (iref) {
self.underlyingImage = [UIImage imageWithCGImage:iref];
}
[self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
}
failureBlock:^(NSError *error) {
self.underlyingImage = nil;
MWLog(@"Photo from asset library error: %@",error);
[self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
}];
} @catch (NSException *e) {
MWLog(@"Photo from asset library error: %@", e);
[self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
}
}
});
} else if ([_photoURL isFileReferenceURL]) {
// Load from local file async
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@autoreleasepool {
@try {
self.underlyingImage = [UIImage imageWithContentsOfFile:_photoURL.path];
if (!_underlyingImage) {
MWLog(@"Error loading photo from path: %@", _photoURL.path);
}
} @finally {
[self performSelectorOnMainThread:@selector(imageLoadingComplete) withObject:nil waitUntilDone:NO];
}
}
});
} else {
// Load async from web (using SDWebImage)
@try {
SDWebImageManager *manager = [SDWebImageManager sharedManager];
_webImageOperation = [manager downloadImageWithURL:_photoURL
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
if (expectedSize > 0) {
float progress = receivedSize / (float)expectedSize;
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:progress], @"progress",
self, @"photo", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_PROGRESS_NOTIFICATION object:dict];
}
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (error) {
MWLog(@"SDWebImage failed to download image: %@", error);
}
_webImageOperation = nil;
self.underlyingImage = image;
[self imageLoadingComplete];
}];
} @catch (NSException *e) {
MWLog(@"Photo from web: %@", e);
_webImageOperation = nil;
[self imageLoadingComplete];
}
}
} else {
// Failed - no source
@throw [NSException exceptionWithName:nil reason:nil userInfo:nil];
}
}
// Release if we can get it again from path or url
- (void)unloadUnderlyingImage {
_loadingInProgress = NO;
self.underlyingImage = nil;
}
- (void)imageLoadingComplete {
NSAssert([[NSThread currentThread] isMainThread], @"This method must be called on the main thread.");
// Complete so notify
_loadingInProgress = NO;
// Notify on next run loop
[self performSelector:@selector(postCompleteNotification) withObject:nil afterDelay:0];
}
- (void)postCompleteNotification {
[[NSNotificationCenter defaultCenter] postNotificationName:MWPHOTO_LOADING_DID_END_NOTIFICATION
object:self];
}
- (void)cancelAnyLoading {
if (_webImageOperation) {
[_webImageOperation cancel];
_loadingInProgress = NO;
}
}
@end
@@ -0,0 +1,71 @@
//
// MWPhotoBrowser.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 14/10/2010.
// Copyright 2010 d3i. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import "MWPhoto.h"
#import "MWPhotoProtocol.h"
#import "MWCaptionView.h"
// Debug Logging
#if 0 // Set to 1 to enable debug logging
#define MWLog(x, ...) NSLog(x, ## __VA_ARGS__);
#else
#define MWLog(x, ...)
#endif
@class MWPhotoBrowser;
@protocol MWPhotoBrowserDelegate <NSObject>
- (NSUInteger)numberOfPhotosInPhotoBrowser:(MWPhotoBrowser *)photoBrowser;
- (id <MWPhoto>)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index;
@optional
- (id <MWPhoto>)photoBrowser:(MWPhotoBrowser *)photoBrowser thumbPhotoAtIndex:(NSUInteger)index;
- (MWCaptionView *)photoBrowser:(MWPhotoBrowser *)photoBrowser captionViewForPhotoAtIndex:(NSUInteger)index;
- (NSString *)photoBrowser:(MWPhotoBrowser *)photoBrowser titleForPhotoAtIndex:(NSUInteger)index;
- (void)photoBrowser:(MWPhotoBrowser *)photoBrowser didDisplayPhotoAtIndex:(NSUInteger)index;
- (void)photoBrowser:(MWPhotoBrowser *)photoBrowser actionButtonPressedForPhotoAtIndex:(NSUInteger)index;
- (BOOL)photoBrowser:(MWPhotoBrowser *)photoBrowser isPhotoSelectedAtIndex:(NSUInteger)index;
- (void)photoBrowser:(MWPhotoBrowser *)photoBrowser photoAtIndex:(NSUInteger)index selectedChanged:(BOOL)selected;
- (void)photoBrowserDidFinishModalPresentation:(MWPhotoBrowser *)photoBrowser;
@end
@interface MWPhotoBrowser : UIViewController <UIScrollViewDelegate, UIActionSheetDelegate, MFMailComposeViewControllerDelegate>
@property (nonatomic, weak) IBOutlet id<MWPhotoBrowserDelegate> delegate;
@property (nonatomic) BOOL zoomPhotosToFill;
@property (nonatomic) BOOL displayNavArrows;
@property (nonatomic) BOOL displayActionButton;
@property (nonatomic) BOOL displaySelectionButtons;
@property (nonatomic) BOOL alwaysShowControls;
@property (nonatomic) BOOL enableGrid;
@property (nonatomic) BOOL enableSwipeToDismiss;
@property (nonatomic) BOOL startOnGrid;
@property (nonatomic) NSUInteger delayToHideElements;
@property (nonatomic, readonly) NSUInteger currentIndex;
// Init
- (id)initWithPhotos:(NSArray *)photosArray __attribute__((deprecated("Use initWithDelegate: instead"))); // Depreciated
- (id)initWithDelegate:(id <MWPhotoBrowserDelegate>)delegate;
// Reloads the photo browser and refetches data
- (void)reloadData;
// Set page that photo browser starts on
- (void)setCurrentPhotoIndex:(NSUInteger)index;
- (void)setInitialPageIndex:(NSUInteger)index __attribute__((deprecated("Use setCurrentPhotoIndex: instead"))); // Depreciated
// Navigation
- (void)showNextPhotoAnimated:(BOOL)animated;
- (void)showPreviousPhotoAnimated:(BOOL)animated;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,136 @@
//
// MWPhotoBrowser_Private.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 08/10/2013.
//
//
#import <UIKit/UIKit.h>
#import "MBProgressHUD.h"
#import "MWGridViewController.h"
#import "MWZoomingScrollView.h"
// Declare private methods of browser
@interface MWPhotoBrowser () {
// Data
NSUInteger _photoCount;
NSMutableArray *_photos;
NSMutableArray *_thumbPhotos;
NSArray *_depreciatedPhotoData; // Depreciated
// Views
UIScrollView *_pagingScrollView;
// Paging & layout
NSMutableSet *_visiblePages, *_recycledPages;
NSUInteger _currentPageIndex;
NSUInteger _previousPageIndex;
CGRect _previousLayoutBounds;
NSUInteger _pageIndexBeforeRotation;
// Navigation & controls
UIToolbar *_toolbar;
NSTimer *_controlVisibilityTimer;
UIBarButtonItem *_previousButton, *_nextButton, *_actionButton, *_doneButton;
MBProgressHUD *_progressHUD;
UIActionSheet *_actionsSheet;
// Grid
MWGridViewController *_gridController;
UIBarButtonItem *_gridPreviousLeftNavItem;
UIBarButtonItem *_gridPreviousRightNavItem;
// Appearance
BOOL _previousNavBarHidden;
BOOL _previousNavBarTranslucent;
UIBarStyle _previousNavBarStyle;
UIStatusBarStyle _previousStatusBarStyle;
UIColor *_previousNavBarTintColor;
UIColor *_previousNavBarBarTintColor;
UIBarButtonItem *_previousViewControllerBackButton;
UIImage *_previousNavigationBarBackgroundImageDefault;
UIImage *_previousNavigationBarBackgroundImageLandscapePhone;
// Misc
BOOL _hasBelongedToViewController;
BOOL _isVCBasedStatusBarAppearance;
BOOL _statusBarShouldBeHidden;
BOOL _displayActionButton;
BOOL _leaveStatusBarAlone;
BOOL _performingLayout;
BOOL _rotating;
BOOL _viewIsActive; // active as in it's in the view heirarchy
BOOL _didSavePreviousStateOfNavBar;
BOOL _skipNextPagingScrollViewPositioning;
BOOL _viewHasAppearedInitially;
CGPoint _currentGridContentOffset;
}
// Properties
@property (nonatomic) UIActivityViewController *activityViewController;
// Layout
- (void)layoutVisiblePages;
- (void)performLayout;
- (BOOL)presentingViewControllerPrefersStatusBarHidden;
// Nav Bar Appearance
- (void)setNavBarAppearance:(BOOL)animated;
- (void)storePreviousNavBarAppearance;
- (void)restorePreviousNavBarAppearance:(BOOL)animated;
// Paging
- (void)tilePages;
- (BOOL)isDisplayingPageForIndex:(NSUInteger)index;
- (MWZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index;
- (MWZoomingScrollView *)pageDisplayingPhoto:(id<MWPhoto>)photo;
- (MWZoomingScrollView *)dequeueRecycledPage;
- (void)configurePage:(MWZoomingScrollView *)page forIndex:(NSUInteger)index;
- (void)didStartViewingPageAtIndex:(NSUInteger)index;
// Frames
- (CGRect)frameForPagingScrollView;
- (CGRect)frameForPageAtIndex:(NSUInteger)index;
- (CGSize)contentSizeForPagingScrollView;
- (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index;
- (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation;
- (CGRect)frameForCaptionView:(MWCaptionView *)captionView atIndex:(NSUInteger)index;
- (CGRect)frameForSelectedButton:(UIButton *)selectedButton atIndex:(NSUInteger)index;
// Navigation
- (void)updateNavigation;
- (void)jumpToPageAtIndex:(NSUInteger)index animated:(BOOL)animated;
- (void)gotoPreviousPage;
- (void)gotoNextPage;
// Grid
- (void)showGrid:(BOOL)animated;
- (void)hideGrid;
// Controls
- (void)cancelControlHiding;
- (void)hideControlsAfterDelay;
- (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent;
- (void)toggleControls;
- (BOOL)areControlsHidden;
// Data
- (NSUInteger)numberOfPhotos;
- (id<MWPhoto>)photoAtIndex:(NSUInteger)index;
- (id<MWPhoto>)thumbPhotoAtIndex:(NSUInteger)index;
- (UIImage *)imageForPhoto:(id<MWPhoto>)photo;
- (BOOL)photoIsSelectedAtIndex:(NSUInteger)index;
- (void)setPhotoSelected:(BOOL)selected atIndex:(NSUInteger)index;
- (void)loadAdjacentPhotosIfNecessary:(id<MWPhoto>)photo;
- (void)releaseAllUnderlyingPhotos:(BOOL)preserveCurrent;
// Actions
- (void)savePhoto;
- (void)copyPhoto;
- (void)emailPhoto;
@end
@@ -0,0 +1,64 @@
//
// MWPhotoProtocol.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 02/01/2012.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import <Foundation/Foundation.h>
// Notifications
#define MWPHOTO_LOADING_DID_END_NOTIFICATION @"MWPHOTO_LOADING_DID_END_NOTIFICATION"
#define MWPHOTO_PROGRESS_NOTIFICATION @"MWPHOTO_PROGRESS_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.
@property (nonatomic, strong) UIImage *underlyingImage;
// Called when the browser has determined the underlying images is not
// already loaded into memory but needs it.
- (void)loadUnderlyingImageAndNotify;
// Fetch the image data from a source and notify when complete.
// 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)performLoadUnderlyingImageAndNotify;
// 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;
// Cancel any background loading of image data
- (void)cancelAnyLoading;
@end
@@ -0,0 +1,27 @@
//
// UIImageViewTap.h
// Momento
//
// Created by Michael Waterfall on 04/11/2009.
// Copyright 2009 d3i. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol MWTapDetectingImageViewDelegate;
@interface MWTapDetectingImageView : UIImageView {}
@property (nonatomic, weak) id <MWTapDetectingImageViewDelegate> tapDelegate;
@end
@protocol MWTapDetectingImageViewDelegate <NSObject>
@optional
- (void)imageView:(UIImageView *)imageView singleTapDetected:(UITouch *)touch;
- (void)imageView:(UIImageView *)imageView doubleTapDetected:(UITouch *)touch;
- (void)imageView:(UIImageView *)imageView tripleTapDetected:(UITouch *)touch;
@end
@@ -0,0 +1,68 @@
//
// UIImageViewTap.m
// Momento
//
// Created by Michael Waterfall on 04/11/2009.
// Copyright 2009 d3i. All rights reserved.
//
#import "MWTapDetectingImageView.h"
@implementation MWTapDetectingImageView
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
self.userInteractionEnabled = YES;
}
return self;
}
- (id)initWithImage:(UIImage *)image {
if ((self = [super initWithImage:image])) {
self.userInteractionEnabled = YES;
}
return self;
}
- (id)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage {
if ((self = [super initWithImage:image highlightedImage:highlightedImage])) {
self.userInteractionEnabled = YES;
}
return self;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSUInteger tapCount = touch.tapCount;
switch (tapCount) {
case 1:
[self handleSingleTap:touch];
break;
case 2:
[self handleDoubleTap:touch];
break;
case 3:
[self handleTripleTap:touch];
break;
default:
break;
}
[[self nextResponder] touchesEnded:touches withEvent:event];
}
- (void)handleSingleTap:(UITouch *)touch {
if ([_tapDelegate respondsToSelector:@selector(imageView:singleTapDetected:)])
[_tapDelegate imageView:self singleTapDetected:touch];
}
- (void)handleDoubleTap:(UITouch *)touch {
if ([_tapDelegate respondsToSelector:@selector(imageView:doubleTapDetected:)])
[_tapDelegate imageView:self doubleTapDetected:touch];
}
- (void)handleTripleTap:(UITouch *)touch {
if ([_tapDelegate respondsToSelector:@selector(imageView:tripleTapDetected:)])
[_tapDelegate imageView:self tripleTapDetected:touch];
}
@end
@@ -0,0 +1,27 @@
//
// UIViewTap.h
// Momento
//
// Created by Michael Waterfall on 04/11/2009.
// Copyright 2009 d3i. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol MWTapDetectingViewDelegate;
@interface MWTapDetectingView : UIView {}
@property (nonatomic, weak) id <MWTapDetectingViewDelegate> tapDelegate;
@end
@protocol MWTapDetectingViewDelegate <NSObject>
@optional
- (void)view:(UIView *)view singleTapDetected:(UITouch *)touch;
- (void)view:(UIView *)view doubleTapDetected:(UITouch *)touch;
- (void)view:(UIView *)view tripleTapDetected:(UITouch *)touch;
@end
@@ -0,0 +1,61 @@
//
// UIViewTap.m
// Momento
//
// Created by Michael Waterfall on 04/11/2009.
// Copyright 2009 d3i. All rights reserved.
//
#import "MWTapDetectingView.h"
@implementation MWTapDetectingView
- (id)init {
if ((self = [super init])) {
self.userInteractionEnabled = YES;
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
self.userInteractionEnabled = YES;
}
return self;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
NSUInteger tapCount = touch.tapCount;
switch (tapCount) {
case 1:
[self handleSingleTap:touch];
break;
case 2:
[self handleDoubleTap:touch];
break;
case 3:
[self handleTripleTap:touch];
break;
default:
break;
}
[[self nextResponder] touchesEnded:touches withEvent:event];
}
- (void)handleSingleTap:(UITouch *)touch {
if ([_tapDelegate respondsToSelector:@selector(view:singleTapDetected:)])
[_tapDelegate view:self singleTapDetected:touch];
}
- (void)handleDoubleTap:(UITouch *)touch {
if ([_tapDelegate respondsToSelector:@selector(view:doubleTapDetected:)])
[_tapDelegate view:self doubleTapDetected:touch];
}
- (void)handleTripleTap:(UITouch *)touch {
if ([_tapDelegate respondsToSelector:@selector(view:tripleTapDetected:)])
[_tapDelegate view:self tripleTapDetected:touch];
}
@end
@@ -0,0 +1,31 @@
//
// ZoomingScrollView.h
// MWPhotoBrowser
//
// Created by Michael Waterfall on 14/10/2010.
// Copyright 2010 d3i. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MWPhotoProtocol.h"
#import "MWTapDetectingImageView.h"
#import "MWTapDetectingView.h"
@class MWPhotoBrowser, MWPhoto, MWCaptionView;
@interface MWZoomingScrollView : UIScrollView <UIScrollViewDelegate, MWTapDetectingImageViewDelegate, MWTapDetectingViewDelegate> {
}
@property () NSUInteger index;
@property (nonatomic) id <MWPhoto> photo;
@property (nonatomic, weak) MWCaptionView *captionView;
@property (nonatomic, weak) UIButton *selectedButton;
- (id)initWithPhotoBrowser:(MWPhotoBrowser *)browser;
- (void)displayImage;
- (void)displayImageFailure;
- (void)setMaxMinZoomScalesForCurrentBounds;
- (void)prepareForReuse;
@end
@@ -0,0 +1,411 @@
//
// ZoomingScrollView.m
// MWPhotoBrowser
//
// Created by Michael Waterfall on 14/10/2010.
// Copyright 2010 d3i. All rights reserved.
//
#import "MWCommon.h"
#import "MWZoomingScrollView.h"
#import "MWPhotoBrowser.h"
#import "MWPhoto.h"
#import "DACircularProgressView.h"
#import "MWPhotoBrowserPrivate.h"
// Private methods and properties
@interface MWZoomingScrollView () {
MWPhotoBrowser __weak *_photoBrowser;
MWTapDetectingView *_tapView; // for background taps
MWTapDetectingImageView *_photoImageView;
DACircularProgressView *_loadingIndicator;
UIImageView *_loadingError;
}
@end
@implementation MWZoomingScrollView
- (id)initWithPhotoBrowser:(MWPhotoBrowser *)browser {
if ((self = [super init])) {
// Setup
_index = NSUIntegerMax;
_photoBrowser = browser;
// Tap view for background
_tapView = [[MWTapDetectingView alloc] initWithFrame:self.bounds];
_tapView.tapDelegate = self;
_tapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_tapView.backgroundColor = [UIColor blackColor];
[self addSubview:_tapView];
// Image view
_photoImageView = [[MWTapDetectingImageView alloc] initWithFrame:CGRectZero];
_photoImageView.tapDelegate = self;
_photoImageView.contentMode = UIViewContentModeCenter;
_photoImageView.backgroundColor = [UIColor blackColor];
[self addSubview:_photoImageView];
// Loading indicator
_loadingIndicator = [[DACircularProgressView alloc] initWithFrame:CGRectMake(140.0f, 30.0f, 40.0f, 40.0f)];
_loadingIndicator.userInteractionEnabled = NO;
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7")) {
_loadingIndicator.thicknessRatio = 0.1;
_loadingIndicator.roundedCorners = NO;
} else {
_loadingIndicator.thicknessRatio = 0.2;
_loadingIndicator.roundedCorners = YES;
}
_loadingIndicator.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;
[self addSubview:_loadingIndicator];
// Listen progress notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(setProgressFromNotification:)
name:MWPHOTO_PROGRESS_NOTIFICATION
object:nil];
// Setup
self.backgroundColor = [UIColor blackColor];
self.delegate = self;
self.showsHorizontalScrollIndicator = NO;
self.showsVerticalScrollIndicator = NO;
self.decelerationRate = UIScrollViewDecelerationRateFast;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)prepareForReuse {
[self hideImageFailure];
self.photo = nil;
self.captionView = nil;
self.selectedButton = nil;
_photoImageView.image = nil;
_index = NSUIntegerMax;
}
#pragma mark - Image
- (void)setPhoto:(id<MWPhoto>)photo {
// Cancel any loading on old photo
if (_photo && photo == nil) {
if ([_photo respondsToSelector:@selector(cancelAnyLoading)]) {
[_photo cancelAnyLoading];
}
}
_photo = photo;
UIImage *img = [_photoBrowser imageForPhoto:_photo];
if (img) {
[self displayImage];
} else {
// Will be loading so show loading
[self showLoadingIndicator];
}
}
// Get and display image
- (void)displayImage {
if (_photo && _photoImageView.image == nil) {
// Reset
self.maximumZoomScale = 1;
self.minimumZoomScale = 1;
self.zoomScale = 1;
self.contentSize = CGSizeMake(0, 0);
// Get image from browser as it handles ordering of fetching
UIImage *img = [_photoBrowser imageForPhoto:_photo];
if (img) {
// Hide indicator
[self hideLoadingIndicator];
// Set image
_photoImageView.image = img;
_photoImageView.hidden = NO;
// Setup photo frame
CGRect photoImageViewFrame;
photoImageViewFrame.origin = CGPointZero;
photoImageViewFrame.size = img.size;
_photoImageView.frame = photoImageViewFrame;
self.contentSize = photoImageViewFrame.size;
// Set zoom to minimum zoom
[self setMaxMinZoomScalesForCurrentBounds];
} else {
// Failed no image
[self displayImageFailure];
}
[self setNeedsLayout];
}
}
// Image failed so just show black!
- (void)displayImageFailure {
[self hideLoadingIndicator];
_photoImageView.image = nil;
if (!_loadingError) {
_loadingError = [UIImageView new];
_loadingError.image = [UIImage imageNamed:@"MWPhotoBrowser.bundle/images/ImageError.png"];
_loadingError.userInteractionEnabled = NO;
_loadingError.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;
[_loadingError sizeToFit];
[self addSubview:_loadingError];
}
_loadingError.frame = CGRectMake(floorf((self.bounds.size.width - _loadingError.frame.size.width) / 2.),
floorf((self.bounds.size.height - _loadingError.frame.size.height) / 2),
_loadingError.frame.size.width,
_loadingError.frame.size.height);
}
- (void)hideImageFailure {
if (_loadingError) {
[_loadingError removeFromSuperview];
_loadingError = nil;
}
}
#pragma mark - Loading Progress
- (void)setProgressFromNotification:(NSNotification *)notification {
NSDictionary *dict = [notification object];
id <MWPhoto> photoWithProgress = [dict objectForKey:@"photo"];
if (photoWithProgress == self.photo) {
float progress = [[dict valueForKey:@"progress"] floatValue];
_loadingIndicator.progress = MAX(MIN(1, progress), 0);
}
}
- (void)hideLoadingIndicator {
_loadingIndicator.hidden = YES;
}
- (void)showLoadingIndicator {
self.zoomScale = 0;
self.minimumZoomScale = 0;
self.maximumZoomScale = 0;
_loadingIndicator.progress = 0;
_loadingIndicator.hidden = NO;
[self hideImageFailure];
}
#pragma mark - Setup
- (CGFloat)initialZoomScaleWithMinScale {
CGFloat zoomScale = self.minimumZoomScale;
if (_photoImageView && _photoBrowser.zoomPhotosToFill) {
// Zoom image to fill if the aspect ratios are fairly similar
CGSize boundsSize = self.bounds.size;
CGSize imageSize = _photoImageView.image.size;
CGFloat boundsAR = boundsSize.width / boundsSize.height;
CGFloat imageAR = imageSize.width / imageSize.height;
CGFloat xScale = boundsSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise
CGFloat yScale = boundsSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise
// Zooms standard portrait images on a 3.5in screen but not on a 4in screen.
if (ABS(boundsAR - imageAR) < 0.17) {
zoomScale = MAX(xScale, yScale);
// Ensure we don't zoom in or out too far, just in case
zoomScale = MIN(MAX(self.minimumZoomScale, zoomScale), self.maximumZoomScale);
}
}
return zoomScale;
}
- (void)setMaxMinZoomScalesForCurrentBounds {
// Reset
self.maximumZoomScale = 1;
self.minimumZoomScale = 1;
self.zoomScale = 1;
// Bail if no image
if (_photoImageView.image == nil) return;
// Reset position
_photoImageView.frame = CGRectMake(0, 0, _photoImageView.frame.size.width, _photoImageView.frame.size.height);
// Sizes
CGSize boundsSize = self.bounds.size;
CGSize imageSize = _photoImageView.image.size;
// Calculate Min
CGFloat xScale = boundsSize.width / imageSize.width; // the scale needed to perfectly fit the image width-wise
CGFloat yScale = boundsSize.height / imageSize.height; // the scale needed to perfectly fit the image height-wise
CGFloat minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible
// Calculate Max
CGFloat maxScale = 3;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// Let them go a bit bigger on a bigger screen!
maxScale = 4;
}
// Image is smaller than screen so no zooming!
if (xScale >= 1 && yScale >= 1) {
minScale = 1.0;
}
// Set min/max zoom
self.maximumZoomScale = maxScale;
self.minimumZoomScale = minScale;
// Initial zoom
self.zoomScale = [self initialZoomScaleWithMinScale];
// If we're zooming to fill then centralise
if (self.zoomScale != minScale) {
// Centralise
self.contentOffset = CGPointMake((imageSize.width * self.zoomScale - boundsSize.width) / 2.0,
(imageSize.height * self.zoomScale - boundsSize.height) / 2.0);
// Disable scrolling initially until the first pinch to fix issues with swiping on an initally zoomed in photo
self.scrollEnabled = NO;
}
// Layout
[self setNeedsLayout];
}
#pragma mark - Layout
- (void)layoutSubviews {
// Update tap view frame
_tapView.frame = self.bounds;
// Position indicators (centre does not seem to work!)
if (!_loadingIndicator.hidden)
_loadingIndicator.frame = CGRectMake(floorf((self.bounds.size.width - _loadingIndicator.frame.size.width) / 2.),
floorf((self.bounds.size.height - _loadingIndicator.frame.size.height) / 2),
_loadingIndicator.frame.size.width,
_loadingIndicator.frame.size.height);
if (_loadingError)
_loadingError.frame = CGRectMake(floorf((self.bounds.size.width - _loadingError.frame.size.width) / 2.),
floorf((self.bounds.size.height - _loadingError.frame.size.height) / 2),
_loadingError.frame.size.width,
_loadingError.frame.size.height);
// Super
[super layoutSubviews];
// Center the image as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
CGRect frameToCenter = _photoImageView.frame;
// Horizontally
if (frameToCenter.size.width < boundsSize.width) {
frameToCenter.origin.x = floorf((boundsSize.width - frameToCenter.size.width) / 2.0);
} else {
frameToCenter.origin.x = 0;
}
// Vertically
if (frameToCenter.size.height < boundsSize.height) {
frameToCenter.origin.y = floorf((boundsSize.height - frameToCenter.size.height) / 2.0);
} else {
frameToCenter.origin.y = 0;
}
// Center
if (!CGRectEqualToRect(_photoImageView.frame, frameToCenter))
_photoImageView.frame = frameToCenter;
}
#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return _photoImageView;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[_photoBrowser cancelControlHiding];
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
self.scrollEnabled = YES; // reset
[_photoBrowser cancelControlHiding];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
[_photoBrowser hideControlsAfterDelay];
}
#pragma mark - Tap Detection
- (void)handleSingleTap:(CGPoint)touchPoint {
[_photoBrowser performSelector:@selector(toggleControls) withObject:nil afterDelay:0.2];
}
- (void)handleDoubleTap:(CGPoint)touchPoint {
// Cancel any single tap handling
[NSObject cancelPreviousPerformRequestsWithTarget:_photoBrowser];
// Zoom
if (self.zoomScale != self.minimumZoomScale && self.zoomScale != [self initialZoomScaleWithMinScale]) {
// Zoom out
[self setZoomScale:self.minimumZoomScale animated:YES];
} else {
// Zoom in to twice the size
CGFloat newZoomScale = ((self.maximumZoomScale + self.minimumZoomScale) / 2);
CGFloat xsize = self.bounds.size.width / newZoomScale;
CGFloat ysize = self.bounds.size.height / newZoomScale;
[self zoomToRect:CGRectMake(touchPoint.x - xsize/2, touchPoint.y - ysize/2, xsize, ysize) animated:YES];
}
// Delay controls
[_photoBrowser hideControlsAfterDelay];
}
// Image View
- (void)imageView:(UIImageView *)imageView singleTapDetected:(UITouch *)touch {
[self handleSingleTap:[touch locationInView:imageView]];
}
- (void)imageView:(UIImageView *)imageView doubleTapDetected:(UITouch *)touch {
[self handleDoubleTap:[touch locationInView:imageView]];
}
// Background View
- (void)view:(UIView *)view singleTapDetected:(UITouch *)touch {
// Translate touch location to image view location
CGFloat touchX = [touch locationInView:view].x;
CGFloat touchY = [touch locationInView:view].y;
touchX *= 1/self.zoomScale;
touchY *= 1/self.zoomScale;
touchX += self.contentOffset.x;
touchY += self.contentOffset.y;
[self handleSingleTap:CGPointMake(touchX, touchY)];
}
- (void)view:(UIView *)view doubleTapDetected:(UITouch *)touch {
// Translate touch location to image view location
CGFloat touchX = [touch locationInView:view].x;
CGFloat touchY = [touch locationInView:view].y;
touchX *= 1/self.zoomScale;
touchY *= 1/self.zoomScale;
touchX += self.contentOffset.x;
touchY += self.contentOffset.y;
[self handleDoubleTap:CGPointMake(touchX, touchY)];
}
@end
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB