add 图片浏览
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |