IDMPhotoBrowser.m 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383
  1. //
  2. // IDMPhotoBrowser.m
  3. // IDMPhotoBrowser
  4. //
  5. // Created by Michael Waterfall on 14/10/2010.
  6. // Copyright 2010 d3i. All rights reserved.
  7. //
  8. #import <QuartzCore/QuartzCore.h>
  9. #import "IDMPhotoBrowser.h"
  10. #import "IDMZoomingScrollView.h"
  11. #import "pop/POP.h"
  12. #ifndef IDMPhotoBrowserLocalizedStrings
  13. #define IDMPhotoBrowserLocalizedStrings(key) \
  14. NSLocalizedStringFromTableInBundle((key), nil, [NSBundle bundleWithPath:[[NSBundle bundleForClass: [IDMPhotoBrowser class]] pathForResource:@"IDMPBLocalizations" ofType:@"bundle"]], nil)
  15. #endif
  16. // Private
  17. @interface IDMPhotoBrowser () {
  18. // Data
  19. NSMutableArray *_photos;
  20. // Views
  21. UIScrollView *_pagingScrollView;
  22. // Gesture
  23. UIPanGestureRecognizer *_panGesture;
  24. // Paging
  25. NSMutableSet *_visiblePages, *_recycledPages;
  26. NSUInteger _pageIndexBeforeRotation;
  27. NSUInteger _currentPageIndex;
  28. // Buttons
  29. UIButton *_doneButton;
  30. // Toolbar
  31. UIToolbar *_toolbar;
  32. UIBarButtonItem *_previousButton, *_nextButton, *_actionButton;
  33. UIBarButtonItem *_counterButton;
  34. UILabel *_counterLabel;
  35. // Actions
  36. UIActionSheet *_actionsSheet;
  37. UIActivityViewController *activityViewController;
  38. // Control
  39. NSTimer *_controlVisibilityTimer;
  40. // Appearance
  41. //UIStatusBarStyle _previousStatusBarStyle;
  42. BOOL _statusBarOriginallyHidden;
  43. // Present
  44. UIView *_senderViewForAnimation;
  45. // Misc
  46. BOOL _performingLayout;
  47. BOOL _rotating;
  48. BOOL _viewIsActive; // active as in it's in the view heirarchy
  49. BOOL _autoHide;
  50. NSInteger _initalPageIndex;
  51. BOOL _isdraggingPhoto;
  52. CGRect _senderViewOriginalFrame;
  53. //UIImage *_backgroundScreenshot;
  54. UIWindow *_applicationWindow;
  55. // iOS 7
  56. UIViewController *_applicationTopViewController;
  57. int _previousModalPresentationStyle;
  58. }
  59. // Private Properties
  60. @property (nonatomic, strong) UIActionSheet *actionsSheet;
  61. @property (nonatomic, strong) UIActivityViewController *activityViewController;
  62. // Private Methods
  63. // Layout
  64. - (void)performLayout;
  65. // Paging
  66. - (void)tilePages;
  67. - (BOOL)isDisplayingPageForIndex:(NSUInteger)index;
  68. - (IDMZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index;
  69. - (IDMZoomingScrollView *)pageDisplayingPhoto:(id<IDMPhoto>)photo;
  70. - (IDMZoomingScrollView *)dequeueRecycledPage;
  71. - (void)configurePage:(IDMZoomingScrollView *)page forIndex:(NSUInteger)index;
  72. - (void)didStartViewingPageAtIndex:(NSUInteger)index;
  73. // Frames
  74. - (CGRect)frameForPagingScrollView;
  75. - (CGRect)frameForPageAtIndex:(NSUInteger)index;
  76. - (CGSize)contentSizeForPagingScrollView;
  77. - (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index;
  78. - (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation;
  79. - (CGRect)frameForDoneButtonAtOrientation:(UIInterfaceOrientation)orientation;
  80. - (CGRect)frameForCaptionView:(IDMCaptionView *)captionView atIndex:(NSUInteger)index;
  81. // Toolbar
  82. - (void)updateToolbar;
  83. // Navigation
  84. - (void)jumpToPageAtIndex:(NSUInteger)index;
  85. - (void)gotoPreviousPage;
  86. - (void)gotoNextPage;
  87. // Controls
  88. - (void)cancelControlHiding;
  89. - (void)hideControlsAfterDelay;
  90. - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent;
  91. //- (void)toggleControls;
  92. - (BOOL)areControlsHidden;
  93. // Interactions
  94. - (void)handleSingleTap;
  95. // Data
  96. - (NSUInteger)numberOfPhotos;
  97. - (id<IDMPhoto>)photoAtIndex:(NSUInteger)index;
  98. - (UIImage *)imageForPhoto:(id<IDMPhoto>)photo;
  99. - (void)loadAdjacentPhotosIfNecessary:(id<IDMPhoto>)photo;
  100. - (void)releaseAllUnderlyingPhotos;
  101. @end
  102. // IDMPhotoBrowser
  103. @implementation IDMPhotoBrowser
  104. // Properties
  105. @synthesize displayDoneButton = _displayDoneButton, displayToolbar = _displayToolbar, displayActionButton = _displayActionButton, displayCounterLabel = _displayCounterLabel, useWhiteBackgroundColor = _useWhiteBackgroundColor, doneButtonImage = _doneButtonImage;
  106. @synthesize leftArrowImage = _leftArrowImage, rightArrowImage = _rightArrowImage, leftArrowSelectedImage = _leftArrowSelectedImage, rightArrowSelectedImage = _rightArrowSelectedImage;
  107. @synthesize displayArrowButton = _displayArrowButton, actionButtonTitles = _actionButtonTitles;
  108. @synthesize arrowButtonsChangePhotosAnimated = _arrowButtonsChangePhotosAnimated;
  109. @synthesize forceHideStatusBar = _forceHideStatusBar;
  110. @synthesize usePopAnimation = _usePopAnimation;
  111. @synthesize disableVerticalSwipe = _disableVerticalSwipe;
  112. @synthesize dismissOnTouch = _dismissOnTouch;
  113. @synthesize actionsSheet = _actionsSheet, activityViewController = _activityViewController;
  114. @synthesize trackTintColor = _trackTintColor, progressTintColor = _progressTintColor;
  115. @synthesize delegate = _delegate;
  116. #pragma mark - NSObject
  117. - (id)init {
  118. if ((self = [super init])) {
  119. // Defaults
  120. self.hidesBottomBarWhenPushed = YES;
  121. _currentPageIndex = 0;
  122. _performingLayout = NO; // Reset on view did appear
  123. _rotating = NO;
  124. _viewIsActive = NO;
  125. _visiblePages = [NSMutableSet new];
  126. _recycledPages = [NSMutableSet new];
  127. _photos = [NSMutableArray new];
  128. _initalPageIndex = 0;
  129. _autoHide = YES;
  130. _autoHideInterface = YES;
  131. _displayDoneButton = YES;
  132. _doneButtonImage = nil;
  133. _displayToolbar = YES;
  134. _displayActionButton = YES;
  135. _displayArrowButton = YES;
  136. _displayCounterLabel = NO;
  137. _forceHideStatusBar = NO;
  138. _usePopAnimation = NO;
  139. _disableVerticalSwipe = NO;
  140. _dismissOnTouch = NO;
  141. _useWhiteBackgroundColor = NO;
  142. _leftArrowImage = _rightArrowImage = _leftArrowSelectedImage = _rightArrowSelectedImage = nil;
  143. _arrowButtonsChangePhotosAnimated = YES;
  144. _backgroundScaleFactor = 1.0;
  145. _animationDuration = 0.28;
  146. _senderViewForAnimation = nil;
  147. _scaleImage = nil;
  148. _isdraggingPhoto = NO;
  149. if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) {
  150. self.automaticallyAdjustsScrollViewInsets = NO;
  151. }
  152. _applicationWindow = [[[UIApplication sharedApplication] delegate] window];
  153. self.modalPresentationStyle = UIModalPresentationCustom;
  154. self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
  155. self.modalPresentationCapturesStatusBarAppearance = YES;
  156. self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
  157. // Listen for IDMPhoto notifications
  158. [[NSNotificationCenter defaultCenter] addObserver:self
  159. selector:@selector(handleIDMPhotoLoadingDidEndNotification:)
  160. name:IDMPhoto_LOADING_DID_END_NOTIFICATION
  161. object:nil];
  162. }
  163. return self;
  164. }
  165. - (id)initWithPhotos:(NSArray *)photosArray {
  166. if ((self = [self init])) {
  167. _photos = [[NSMutableArray alloc] initWithArray:photosArray];
  168. }
  169. return self;
  170. }
  171. - (id)initWithPhotos:(NSArray *)photosArray animatedFromView:(UIView*)view {
  172. if ((self = [self init])) {
  173. _photos = [[NSMutableArray alloc] initWithArray:photosArray];
  174. _senderViewForAnimation = view;
  175. }
  176. return self;
  177. }
  178. - (id)initWithPhotoURLs:(NSArray *)photoURLsArray {
  179. if ((self = [self init])) {
  180. NSArray *photosArray = [IDMPhoto photosWithURLs:photoURLsArray];
  181. _photos = [[NSMutableArray alloc] initWithArray:photosArray];
  182. }
  183. return self;
  184. }
  185. - (id)initWithPhotoURLs:(NSArray *)photoURLsArray animatedFromView:(UIView*)view {
  186. if ((self = [self init])) {
  187. NSArray *photosArray = [IDMPhoto photosWithURLs:photoURLsArray];
  188. _photos = [[NSMutableArray alloc] initWithArray:photosArray];
  189. _senderViewForAnimation = view;
  190. }
  191. return self;
  192. }
  193. - (void)dealloc {
  194. _pagingScrollView.delegate = nil;
  195. [[NSNotificationCenter defaultCenter] removeObserver:self];
  196. [self releaseAllUnderlyingPhotos];
  197. }
  198. - (void)releaseAllUnderlyingPhotos {
  199. for (id p in _photos) { if (p != [NSNull null]) [p unloadUnderlyingImage]; } // Release photos
  200. }
  201. - (void)didReceiveMemoryWarning {
  202. // Release any cached data, images, etc that aren't in use.
  203. [self releaseAllUnderlyingPhotos];
  204. [_recycledPages removeAllObjects];
  205. // Releases the view if it doesn't have a superview.
  206. [super didReceiveMemoryWarning];
  207. }
  208. #pragma mark - Pan Gesture
  209. - (void)panGestureRecognized:(id)sender {
  210. // Initial Setup
  211. IDMZoomingScrollView *scrollView = [self pageDisplayedAtIndex:_currentPageIndex];
  212. //IDMTapDetectingImageView *scrollView.photoImageView = scrollView.photoImageView;
  213. static float firstX, firstY;
  214. float viewHeight = scrollView.frame.size.height;
  215. float viewHalfHeight = viewHeight/2;
  216. CGPoint translatedPoint = [(UIPanGestureRecognizer*)sender translationInView:self.view];
  217. // Gesture Began
  218. if ([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {
  219. [self setControlsHidden:YES animated:YES permanent:YES];
  220. firstX = [scrollView center].x;
  221. firstY = [scrollView center].y;
  222. _senderViewForAnimation.hidden = (_currentPageIndex == _initalPageIndex);
  223. _isdraggingPhoto = YES;
  224. [self setNeedsStatusBarAppearanceUpdate];
  225. }
  226. translatedPoint = CGPointMake(firstX, firstY+translatedPoint.y);
  227. [scrollView setCenter:translatedPoint];
  228. float newY = scrollView.center.y - viewHalfHeight;
  229. float newAlpha = 1 - fabsf(newY)/viewHeight; //abs(newY)/viewHeight * 1.8;
  230. self.view.opaque = YES;
  231. self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:newAlpha];
  232. // Gesture Ended
  233. if ([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {
  234. if(scrollView.center.y > viewHalfHeight+40 || scrollView.center.y < viewHalfHeight-40) // Automatic Dismiss View
  235. {
  236. if (_senderViewForAnimation && _currentPageIndex == _initalPageIndex) {
  237. [self performCloseAnimationWithScrollView:scrollView];
  238. return;
  239. }
  240. CGFloat finalX = firstX, finalY;
  241. CGFloat windowsHeigt = [_applicationWindow frame].size.height;
  242. if(scrollView.center.y > viewHalfHeight+30) // swipe down
  243. finalY = windowsHeigt*2;
  244. else // swipe up
  245. finalY = -viewHalfHeight;
  246. CGFloat animationDuration = 0.35;
  247. [UIView beginAnimations:nil context:NULL];
  248. [UIView setAnimationDuration:animationDuration];
  249. [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
  250. [UIView setAnimationDelegate:self];
  251. [scrollView setCenter:CGPointMake(finalX, finalY)];
  252. self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0];
  253. [UIView commitAnimations];
  254. [self performSelector:@selector(doneButtonPressed:) withObject:self afterDelay:animationDuration];
  255. }
  256. else // Continue Showing View
  257. {
  258. _isdraggingPhoto = NO;
  259. [self setNeedsStatusBarAppearanceUpdate];
  260. self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:1];
  261. CGFloat velocityY = (.35*[(UIPanGestureRecognizer*)sender velocityInView:self.view].y);
  262. CGFloat finalX = firstX;
  263. CGFloat finalY = viewHalfHeight;
  264. CGFloat animationDuration = (ABS(velocityY)*.0002)+.2;
  265. [UIView beginAnimations:nil context:NULL];
  266. [UIView setAnimationDuration:animationDuration];
  267. [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
  268. [UIView setAnimationDelegate:self];
  269. [scrollView setCenter:CGPointMake(finalX, finalY)];
  270. [UIView commitAnimations];
  271. }
  272. }
  273. }
  274. #pragma mark - Animation
  275. - (void)performPresentAnimation {
  276. self.view.alpha = 0.0f;
  277. _pagingScrollView.alpha = 0.0f;
  278. UIImage *imageFromView = _scaleImage ? _scaleImage : [self getImageFromView:_senderViewForAnimation];
  279. _senderViewOriginalFrame = [_senderViewForAnimation.superview convertRect:_senderViewForAnimation.frame toView:nil];
  280. UIView *fadeView = [[UIView alloc] initWithFrame:_applicationWindow.bounds];
  281. fadeView.backgroundColor = [UIColor clearColor];
  282. [_applicationWindow addSubview:fadeView];
  283. UIImageView *resizableImageView = [[UIImageView alloc] initWithImage:imageFromView];
  284. resizableImageView.frame = _senderViewOriginalFrame;
  285. resizableImageView.clipsToBounds = YES;
  286. resizableImageView.contentMode = _senderViewForAnimation ? _senderViewForAnimation.contentMode : UIViewContentModeScaleAspectFill;
  287. resizableImageView.backgroundColor = [UIColor clearColor];
  288. [_applicationWindow addSubview:resizableImageView];
  289. _senderViewForAnimation.hidden = YES;
  290. void (^completion)() = ^() {
  291. self.view.alpha = 1.0f;
  292. _pagingScrollView.alpha = 1.0f;
  293. resizableImageView.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor) ? 1 : 0 alpha:1];
  294. [fadeView removeFromSuperview];
  295. [resizableImageView removeFromSuperview];
  296. };
  297. [UIView animateWithDuration:_animationDuration animations:^{
  298. fadeView.backgroundColor = self.useWhiteBackgroundColor ? [UIColor whiteColor] : [UIColor blackColor];
  299. } completion:nil];
  300. CGRect finalImageViewFrame = [self animationFrameForImage:imageFromView presenting:YES scrollView:nil];
  301. if(_usePopAnimation)
  302. {
  303. [self animateView:resizableImageView
  304. toFrame:finalImageViewFrame
  305. completion:completion];
  306. }
  307. else
  308. {
  309. [UIView animateWithDuration:_animationDuration animations:^{
  310. resizableImageView.layer.frame = finalImageViewFrame;
  311. } completion:^(BOOL finished) {
  312. completion();
  313. }];
  314. }
  315. }
  316. - (void)performCloseAnimationWithScrollView:(IDMZoomingScrollView*)scrollView {
  317. if ([_delegate respondsToSelector:@selector(willDisappearPhotoBrowser:)]) {
  318. [_delegate willDisappearPhotoBrowser:self];
  319. }
  320. float fadeAlpha = 1 - fabs(scrollView.frame.origin.y)/scrollView.frame.size.height;
  321. UIImage *imageFromView = [scrollView.photo underlyingImage];
  322. if (!imageFromView && [scrollView.photo respondsToSelector:@selector(placeholderImage)]) {
  323. imageFromView = [scrollView.photo placeholderImage];
  324. }
  325. UIView *fadeView = [[UIView alloc] initWithFrame:_applicationWindow.bounds];
  326. fadeView.backgroundColor = self.useWhiteBackgroundColor ? [UIColor whiteColor] : [UIColor blackColor];
  327. fadeView.alpha = fadeAlpha;
  328. [_applicationWindow addSubview:fadeView];
  329. CGRect imageViewFrame = [self animationFrameForImage:imageFromView presenting:NO scrollView:scrollView];
  330. UIImageView *resizableImageView = [[UIImageView alloc] initWithImage:imageFromView];
  331. resizableImageView.frame = imageViewFrame;
  332. resizableImageView.contentMode = _senderViewForAnimation ? _senderViewForAnimation.contentMode : UIViewContentModeScaleAspectFill;
  333. resizableImageView.backgroundColor = [UIColor clearColor];
  334. resizableImageView.clipsToBounds = YES;
  335. [_applicationWindow addSubview:resizableImageView];
  336. self.view.hidden = YES;
  337. void (^completion)() = ^() {
  338. _senderViewForAnimation.hidden = NO;
  339. _senderViewForAnimation = nil;
  340. _scaleImage = nil;
  341. [fadeView removeFromSuperview];
  342. [resizableImageView removeFromSuperview];
  343. [self prepareForClosePhotoBrowser];
  344. [self dismissPhotoBrowserAnimated:NO];
  345. };
  346. [UIView animateWithDuration:_animationDuration animations:^{
  347. fadeView.alpha = 0;
  348. self.view.backgroundColor = [UIColor clearColor];
  349. } completion:nil];
  350. CGRect senderViewOriginalFrame = _senderViewForAnimation.superview ? [_senderViewForAnimation.superview convertRect:_senderViewForAnimation.frame toView:nil] : _senderViewOriginalFrame;
  351. if(_usePopAnimation)
  352. {
  353. [self animateView:resizableImageView
  354. toFrame:senderViewOriginalFrame
  355. completion:completion];
  356. }
  357. else
  358. {
  359. [UIView animateWithDuration:_animationDuration animations:^{
  360. resizableImageView.layer.frame = senderViewOriginalFrame;
  361. } completion:^(BOOL finished) {
  362. completion();
  363. }];
  364. }
  365. }
  366. - (CGRect)animationFrameForImage:(UIImage *)image presenting:(BOOL)presenting scrollView:(UIScrollView *)scrollView
  367. {
  368. if (!image) {
  369. return CGRectZero;
  370. }
  371. CGSize imageSize = image.size;
  372. CGFloat maxWidth = CGRectGetWidth(_applicationWindow.bounds);
  373. CGFloat maxHeight = CGRectGetHeight(_applicationWindow.bounds);
  374. CGRect animationFrame = CGRectZero;
  375. CGFloat aspect = imageSize.width / imageSize.height;
  376. if (maxWidth / aspect <= maxHeight) {
  377. animationFrame.size = CGSizeMake(maxWidth, maxWidth / aspect);
  378. }
  379. else {
  380. animationFrame.size = CGSizeMake(maxHeight * aspect, maxHeight);
  381. }
  382. animationFrame.origin.x = roundf((maxWidth - animationFrame.size.width) / 2.0f);
  383. animationFrame.origin.y = roundf((maxHeight - animationFrame.size.height) / 2.0f);
  384. if (!presenting) {
  385. animationFrame.origin.y += scrollView.frame.origin.y;
  386. }
  387. return animationFrame;
  388. }
  389. #pragma mark - Genaral
  390. - (void)prepareForClosePhotoBrowser {
  391. // Gesture
  392. [_applicationWindow removeGestureRecognizer:_panGesture];
  393. _autoHide = NO;
  394. // Controls
  395. [NSObject cancelPreviousPerformRequestsWithTarget:self]; // Cancel any pending toggles from taps
  396. }
  397. - (void)dismissPhotoBrowserAnimated:(BOOL)animated {
  398. self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
  399. if ([_delegate respondsToSelector:@selector(photoBrowser:willDismissAtPageIndex:)])
  400. [_delegate photoBrowser:self willDismissAtPageIndex:_currentPageIndex];
  401. [self dismissViewControllerAnimated:animated completion:^{
  402. if ([_delegate respondsToSelector:@selector(photoBrowser:didDismissAtPageIndex:)])
  403. [_delegate photoBrowser:self didDismissAtPageIndex:_currentPageIndex];
  404. // if (SYSTEM_VERSION_LESS_THAN(@"8.0"))
  405. // {
  406. // _applicationTopViewController.modalPresentationStyle = _previousModalPresentationStyle;
  407. // }
  408. }];
  409. }
  410. - (UIButton*)customToolbarButtonImage:(UIImage*)image imageSelected:(UIImage*)selectedImage action:(SEL)action {
  411. UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
  412. [button setImage:image forState:UIControlStateNormal];
  413. [button setImage:selectedImage forState:UIControlStateDisabled];
  414. [button addTarget:self action:action forControlEvents:UIControlEventTouchUpInside];
  415. [button setContentMode:UIViewContentModeCenter];
  416. [button setFrame:[self getToolbarButtonFrame:image]];
  417. return button;
  418. }
  419. - (CGRect)getToolbarButtonFrame:(UIImage *)image{
  420. BOOL const isRetinaHd = ((float)[[UIScreen mainScreen] scale] > 2.0f);
  421. float const defaultButtonSize = isRetinaHd ? 66.0f : 44.0f;
  422. CGFloat buttonWidth = (image.size.width > defaultButtonSize) ? image.size.width : defaultButtonSize;
  423. CGFloat buttonHeight = (image.size.height > defaultButtonSize) ? image.size.width : defaultButtonSize;
  424. return CGRectMake(0,0, buttonWidth, buttonHeight);
  425. }
  426. - (UIImage*)getImageFromView:(UIView *)view {
  427. UIGraphicsBeginImageContextWithOptions(view.bounds.size, YES, 2);
  428. [view.layer renderInContext:UIGraphicsGetCurrentContext()];
  429. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  430. UIGraphicsEndImageContext();
  431. return image;
  432. }
  433. - (UIViewController *)topviewController
  434. {
  435. UIViewController *topviewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  436. while (topviewController.presentedViewController) {
  437. topviewController = topviewController.presentedViewController;
  438. }
  439. return topviewController;
  440. }
  441. #pragma mark - View Lifecycle
  442. - (void)viewDidLoad {
  443. // View
  444. self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:1];
  445. self.view.clipsToBounds = YES;
  446. // Setup paging scrolling view
  447. CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
  448. _pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
  449. //_pagingScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  450. _pagingScrollView.pagingEnabled = YES;
  451. _pagingScrollView.delegate = self;
  452. _pagingScrollView.showsHorizontalScrollIndicator = NO;
  453. _pagingScrollView.showsVerticalScrollIndicator = NO;
  454. _pagingScrollView.backgroundColor = [UIColor clearColor];
  455. _pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
  456. [self.view addSubview:_pagingScrollView];
  457. // Transition animation
  458. [self performPresentAnimation];
  459. UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;
  460. // Toolbar
  461. _toolbar = [[UIToolbar alloc] initWithFrame:[self frameForToolbarAtOrientation:currentOrientation]];
  462. _toolbar.backgroundColor = [UIColor clearColor];
  463. _toolbar.clipsToBounds = YES;
  464. _toolbar.translucent = YES;
  465. [_toolbar setBackgroundImage:[UIImage new]
  466. forToolbarPosition:UIToolbarPositionAny
  467. barMetrics:UIBarMetricsDefault];
  468. // Close Button
  469. _doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
  470. [_doneButton setFrame:[self frameForDoneButtonAtOrientation:currentOrientation]];
  471. [_doneButton setAlpha:1.0f];
  472. [_doneButton addTarget:self action:@selector(doneButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
  473. if(!_doneButtonImage) {
  474. [_doneButton setTitleColor:[UIColor colorWithWhite:0.9 alpha:0.9] forState:UIControlStateNormal|UIControlStateHighlighted];
  475. [_doneButton setTitle:IDMPhotoBrowserLocalizedStrings(@"Done") forState:UIControlStateNormal];
  476. [_doneButton.titleLabel setFont:[UIFont boldSystemFontOfSize:11.0f]];
  477. [_doneButton setBackgroundColor:[UIColor colorWithWhite:0.1 alpha:0.5]];
  478. _doneButton.layer.cornerRadius = 3.0f;
  479. _doneButton.layer.borderColor = [UIColor colorWithWhite:0.9 alpha:0.9].CGColor;
  480. _doneButton.layer.borderWidth = 1.0f;
  481. }
  482. else {
  483. [_doneButton setImage:_doneButtonImage forState:UIControlStateNormal];
  484. _doneButton.contentMode = UIViewContentModeScaleAspectFit;
  485. }
  486. UIImage *leftButtonImage = (_leftArrowImage == nil) ?
  487. [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft.png"] : _leftArrowImage;
  488. UIImage *rightButtonImage = (_rightArrowImage == nil) ?
  489. [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight.png"] : _rightArrowImage;
  490. UIImage *leftButtonSelectedImage = (_leftArrowSelectedImage == nil) ?
  491. [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeftSelected.png"] : _leftArrowSelectedImage;
  492. UIImage *rightButtonSelectedImage = (_rightArrowSelectedImage == nil) ?
  493. [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRightSelected.png"] : _rightArrowSelectedImage;
  494. // Arrows
  495. _previousButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:leftButtonImage
  496. imageSelected:leftButtonSelectedImage
  497. action:@selector(gotoPreviousPage)]];
  498. _nextButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:rightButtonImage
  499. imageSelected:rightButtonSelectedImage
  500. action:@selector(gotoNextPage)]];
  501. // Counter Label
  502. _counterLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 95, 40)];
  503. _counterLabel.textAlignment = NSTextAlignmentCenter;
  504. _counterLabel.backgroundColor = [UIColor clearColor];
  505. _counterLabel.font = [UIFont fontWithName:@"Helvetica" size:17];
  506. if(_useWhiteBackgroundColor == NO) {
  507. _counterLabel.textColor = [UIColor whiteColor];
  508. _counterLabel.shadowColor = [UIColor darkTextColor];
  509. _counterLabel.shadowOffset = CGSizeMake(0, 1);
  510. }
  511. else {
  512. _counterLabel.textColor = [UIColor blackColor];
  513. }
  514. // Counter Button
  515. _counterButton = [[UIBarButtonItem alloc] initWithCustomView:_counterLabel];
  516. // Action Button
  517. _actionButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
  518. target:self
  519. action:@selector(actionButtonPressed:)];
  520. // Gesture
  521. _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognized:)];
  522. [_panGesture setMinimumNumberOfTouches:1];
  523. [_panGesture setMaximumNumberOfTouches:1];
  524. // Update
  525. //[self reloadData];
  526. // Super
  527. [super viewDidLoad];
  528. }
  529. - (void)viewWillAppear:(BOOL)animated {
  530. // Update
  531. [self reloadData];
  532. if ([_delegate respondsToSelector:@selector(willAppearPhotoBrowser:)]) {
  533. [_delegate willAppearPhotoBrowser:self];
  534. }
  535. // Super
  536. [super viewWillAppear:animated];
  537. // Status Bar
  538. _statusBarOriginallyHidden = [UIApplication sharedApplication].statusBarHidden;
  539. // Update UI
  540. [self hideControlsAfterDelay];
  541. }
  542. - (void)viewDidAppear:(BOOL)animated {
  543. [super viewDidAppear:animated];
  544. _viewIsActive = YES;
  545. }
  546. // Release any retained subviews of the main view.
  547. - (void)viewDidUnload {
  548. _currentPageIndex = 0;
  549. _pagingScrollView = nil;
  550. _visiblePages = nil;
  551. _recycledPages = nil;
  552. _toolbar = nil;
  553. _doneButton = nil;
  554. _previousButton = nil;
  555. _nextButton = nil;
  556. [super viewDidUnload];
  557. }
  558. #pragma mark - Status Bar
  559. - (UIStatusBarStyle)preferredStatusBarStyle {
  560. return _useWhiteBackgroundColor ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent;
  561. }
  562. - (BOOL)prefersStatusBarHidden {
  563. if(_forceHideStatusBar) {
  564. return YES;
  565. }
  566. if(_isdraggingPhoto) {
  567. if(_statusBarOriginallyHidden) {
  568. return YES;
  569. }
  570. else {
  571. return NO;
  572. }
  573. }
  574. else {
  575. return [self areControlsHidden];
  576. }
  577. }
  578. - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
  579. return UIStatusBarAnimationFade;
  580. }
  581. #pragma mark - Layout
  582. - (void)viewWillLayoutSubviews {
  583. // Flag
  584. _performingLayout = YES;
  585. UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;
  586. // Toolbar
  587. _toolbar.frame = [self frameForToolbarAtOrientation:currentOrientation];
  588. // Done button
  589. _doneButton.frame = [self frameForDoneButtonAtOrientation:currentOrientation];
  590. // Remember index
  591. NSUInteger indexPriorToLayout = _currentPageIndex;
  592. // Get paging scroll view frame to determine if anything needs changing
  593. CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
  594. // Frame needs changing
  595. _pagingScrollView.frame = pagingScrollViewFrame;
  596. // Recalculate contentSize based on current orientation
  597. _pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
  598. // Adjust frames and configuration of each visible page
  599. for (IDMZoomingScrollView *page in _visiblePages) {
  600. NSUInteger index = PAGE_INDEX(page);
  601. page.frame = [self frameForPageAtIndex:index];
  602. page.captionView.frame = [self frameForCaptionView:page.captionView atIndex:index];
  603. [page setMaxMinZoomScalesForCurrentBounds];
  604. }
  605. // Adjust contentOffset to preserve page location based on values collected prior to location
  606. _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:indexPriorToLayout];
  607. [self didStartViewingPageAtIndex:_currentPageIndex]; // initial
  608. // Reset
  609. _currentPageIndex = indexPriorToLayout;
  610. _performingLayout = NO;
  611. // Super
  612. [super viewWillLayoutSubviews];
  613. }
  614. - (void)performLayout {
  615. // Setup
  616. _performingLayout = YES;
  617. NSUInteger numberOfPhotos = [self numberOfPhotos];
  618. // Setup pages
  619. [_visiblePages removeAllObjects];
  620. [_recycledPages removeAllObjects];
  621. // Toolbar
  622. if (_displayToolbar) {
  623. [self.view addSubview:_toolbar];
  624. } else {
  625. [_toolbar removeFromSuperview];
  626. }
  627. // Close button
  628. if(_displayDoneButton && !self.navigationController.navigationBar)
  629. [self.view addSubview:_doneButton];
  630. // Toolbar items & navigation
  631. UIBarButtonItem *fixedLeftSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
  632. target:self action:nil];
  633. fixedLeftSpace.width = 32; // To balance action button
  634. UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
  635. target:self action:nil];
  636. NSMutableArray *items = [NSMutableArray new];
  637. if (_displayActionButton)
  638. [items addObject:fixedLeftSpace];
  639. [items addObject:flexSpace];
  640. if (numberOfPhotos > 1 && _displayArrowButton)
  641. [items addObject:_previousButton];
  642. if(_displayCounterLabel) {
  643. [items addObject:flexSpace];
  644. [items addObject:_counterButton];
  645. }
  646. [items addObject:flexSpace];
  647. if (numberOfPhotos > 1 && _displayArrowButton)
  648. [items addObject:_nextButton];
  649. [items addObject:flexSpace];
  650. if(_displayActionButton)
  651. [items addObject:_actionButton];
  652. [_toolbar setItems:items];
  653. [self updateToolbar];
  654. // Content offset
  655. _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:_currentPageIndex];
  656. [self tilePages];
  657. _performingLayout = NO;
  658. if(! _disableVerticalSwipe) {
  659. [self.view addGestureRecognizer:_panGesture];
  660. }
  661. }
  662. #pragma mark - Data
  663. - (void)reloadData {
  664. // Get data
  665. [self releaseAllUnderlyingPhotos];
  666. // Update
  667. [self performLayout];
  668. // Layout
  669. [self.view setNeedsLayout];
  670. }
  671. - (NSUInteger)numberOfPhotos {
  672. return _photos.count;
  673. }
  674. - (id<IDMPhoto>)photoAtIndex:(NSUInteger)index {
  675. return _photos[index];
  676. }
  677. - (IDMCaptionView *)captionViewForPhotoAtIndex:(NSUInteger)index {
  678. IDMCaptionView *captionView = nil;
  679. if ([_delegate respondsToSelector:@selector(photoBrowser:captionViewForPhotoAtIndex:)]) {
  680. captionView = [_delegate photoBrowser:self captionViewForPhotoAtIndex:index];
  681. } else {
  682. id <IDMPhoto> photo = [self photoAtIndex:index];
  683. if ([photo respondsToSelector:@selector(caption)]) {
  684. if ([photo caption]) captionView = [[IDMCaptionView alloc] initWithPhoto:photo];
  685. }
  686. }
  687. captionView.alpha = [self areControlsHidden] ? 0 : 1; // Initial alpha
  688. return captionView;
  689. }
  690. - (UIImage *)imageForPhoto:(id<IDMPhoto>)photo {
  691. if (photo) {
  692. // Get image or obtain in background
  693. if ([photo underlyingImage]) {
  694. return [photo underlyingImage];
  695. } else {
  696. [photo loadUnderlyingImageAndNotify];
  697. if ([photo respondsToSelector:@selector(placeholderImage)]) {
  698. return [photo placeholderImage];
  699. }
  700. }
  701. }
  702. return nil;
  703. }
  704. - (void)loadAdjacentPhotosIfNecessary:(id<IDMPhoto>)photo {
  705. IDMZoomingScrollView *page = [self pageDisplayingPhoto:photo];
  706. if (page) {
  707. // If page is current page then initiate loading of previous and next pages
  708. NSUInteger pageIndex = PAGE_INDEX(page);
  709. if (_currentPageIndex == pageIndex) {
  710. if (pageIndex > 0) {
  711. // Preload index - 1
  712. id <IDMPhoto> photo = [self photoAtIndex:pageIndex-1];
  713. if (![photo underlyingImage]) {
  714. [photo loadUnderlyingImageAndNotify];
  715. IDMLog(@"Pre-loading image at index %i", pageIndex-1);
  716. }
  717. }
  718. if (pageIndex < [self numberOfPhotos] - 1) {
  719. // Preload index + 1
  720. id <IDMPhoto> photo = [self photoAtIndex:pageIndex+1];
  721. if (![photo underlyingImage]) {
  722. [photo loadUnderlyingImageAndNotify];
  723. IDMLog(@"Pre-loading image at index %i", pageIndex+1);
  724. }
  725. }
  726. }
  727. }
  728. }
  729. #pragma mark - IDMPhoto Loading Notification
  730. - (void)handleIDMPhotoLoadingDidEndNotification:(NSNotification *)notification {
  731. id <IDMPhoto> photo = [notification object];
  732. IDMZoomingScrollView *page = [self pageDisplayingPhoto:photo];
  733. if (page) {
  734. if ([photo underlyingImage]) {
  735. // Successful load
  736. [page displayImage];
  737. [self loadAdjacentPhotosIfNecessary:photo];
  738. } else {
  739. // Failed to load
  740. [page displayImageFailure];
  741. }
  742. }
  743. }
  744. #pragma mark - Paging
  745. - (void)tilePages {
  746. // Calculate which pages should be visible
  747. // Ignore padding as paging bounces encroach on that
  748. // and lead to false page loads
  749. CGRect visibleBounds = _pagingScrollView.bounds;
  750. NSInteger iFirstIndex = (NSInteger) floorf((CGRectGetMinX(visibleBounds)+PADDING*2) / CGRectGetWidth(visibleBounds));
  751. NSInteger iLastIndex = (NSInteger) floorf((CGRectGetMaxX(visibleBounds)-PADDING*2-1) / CGRectGetWidth(visibleBounds));
  752. if (iFirstIndex < 0) iFirstIndex = 0;
  753. if (iFirstIndex > [self numberOfPhotos] - 1) iFirstIndex = [self numberOfPhotos] - 1;
  754. if (iLastIndex < 0) iLastIndex = 0;
  755. if (iLastIndex > [self numberOfPhotos] - 1) iLastIndex = [self numberOfPhotos] - 1;
  756. // Recycle no longer needed pages
  757. NSInteger pageIndex;
  758. for (IDMZoomingScrollView *page in _visiblePages) {
  759. pageIndex = PAGE_INDEX(page);
  760. if (pageIndex < (NSUInteger)iFirstIndex || pageIndex > (NSUInteger)iLastIndex) {
  761. [_recycledPages addObject:page];
  762. [page prepareForReuse];
  763. [page removeFromSuperview];
  764. IDMLog(@"Removed page at index %i", PAGE_INDEX(page));
  765. }
  766. }
  767. [_visiblePages minusSet:_recycledPages];
  768. while (_recycledPages.count > 2) // Only keep 2 recycled pages
  769. [_recycledPages removeObject:[_recycledPages anyObject]];
  770. // Add missing pages
  771. for (NSUInteger index = (NSUInteger)iFirstIndex; index <= (NSUInteger)iLastIndex; index++) {
  772. if (![self isDisplayingPageForIndex:index]) {
  773. // Add new page
  774. IDMZoomingScrollView *page;
  775. page = [[IDMZoomingScrollView alloc] initWithPhotoBrowser:self];
  776. page.backgroundColor = [UIColor clearColor];
  777. page.opaque = YES;
  778. [self configurePage:page forIndex:index];
  779. [_visiblePages addObject:page];
  780. [_pagingScrollView addSubview:page];
  781. IDMLog(@"Added page at index %i", index);
  782. // Add caption
  783. IDMCaptionView *captionView = [self captionViewForPhotoAtIndex:index];
  784. captionView.frame = [self frameForCaptionView:captionView atIndex:index];
  785. [_pagingScrollView addSubview:captionView];
  786. page.captionView = captionView;
  787. }
  788. }
  789. }
  790. - (BOOL)isDisplayingPageForIndex:(NSUInteger)index {
  791. for (IDMZoomingScrollView *page in _visiblePages)
  792. if (PAGE_INDEX(page) == index) return YES;
  793. return NO;
  794. }
  795. - (IDMZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index {
  796. IDMZoomingScrollView *thePage = nil;
  797. for (IDMZoomingScrollView *page in _visiblePages) {
  798. if (PAGE_INDEX(page) == index) {
  799. thePage = page; break;
  800. }
  801. }
  802. return thePage;
  803. }
  804. - (IDMZoomingScrollView *)pageDisplayingPhoto:(id<IDMPhoto>)photo {
  805. IDMZoomingScrollView *thePage = nil;
  806. for (IDMZoomingScrollView *page in _visiblePages) {
  807. if (page.photo == photo) {
  808. thePage = page; break;
  809. }
  810. }
  811. return thePage;
  812. }
  813. - (void)configurePage:(IDMZoomingScrollView *)page forIndex:(NSUInteger)index {
  814. page.frame = [self frameForPageAtIndex:index];
  815. page.tag = PAGE_INDEX_TAG_OFFSET + index;
  816. page.photo = [self photoAtIndex:index];
  817. __block __weak IDMPhoto *photo = (IDMPhoto*)page.photo;
  818. __weak IDMZoomingScrollView* weakPage = page;
  819. photo.progressUpdateBlock = ^(CGFloat progress){
  820. [weakPage setProgress:progress forPhoto:photo];
  821. };
  822. }
  823. - (IDMZoomingScrollView *)dequeueRecycledPage {
  824. IDMZoomingScrollView *page = [_recycledPages anyObject];
  825. if (page) {
  826. [_recycledPages removeObject:page];
  827. }
  828. return page;
  829. }
  830. // Handle page changes
  831. - (void)didStartViewingPageAtIndex:(NSUInteger)index {
  832. // Load adjacent images if needed and the photo is already
  833. // loaded. Also called after photo has been loaded in background
  834. id <IDMPhoto> currentPhoto = [self photoAtIndex:index];
  835. if ([currentPhoto underlyingImage]) {
  836. // photo loaded so load ajacent now
  837. [self loadAdjacentPhotosIfNecessary:currentPhoto];
  838. }
  839. if ([_delegate respondsToSelector:@selector(photoBrowser:didShowPhotoAtIndex:)]) {
  840. [_delegate photoBrowser:self didShowPhotoAtIndex:index];
  841. }
  842. }
  843. #pragma mark - Frame Calculations
  844. - (CGRect)frameForPagingScrollView {
  845. CGRect frame = self.view.bounds;
  846. frame.origin.x -= PADDING;
  847. frame.size.width += (2 * PADDING);
  848. return frame;
  849. }
  850. - (CGRect)frameForPageAtIndex:(NSUInteger)index {
  851. // We have to use our paging scroll view's bounds, not frame, to calculate the page placement. When the device is in
  852. // landscape orientation, the frame will still be in portrait because the pagingScrollView is the root view controller's
  853. // view, so its frame is in window coordinate space, which is never rotated. Its bounds, however, will be in landscape
  854. // because it has a rotation transform applied.
  855. CGRect bounds = _pagingScrollView.bounds;
  856. CGRect pageFrame = bounds;
  857. pageFrame.size.width -= (2 * PADDING);
  858. pageFrame.origin.x = (bounds.size.width * index) + PADDING;
  859. return pageFrame;
  860. }
  861. - (CGSize)contentSizeForPagingScrollView {
  862. // We have to use the paging scroll view's bounds to calculate the contentSize, for the same reason outlined above.
  863. CGRect bounds = _pagingScrollView.bounds;
  864. return CGSizeMake(bounds.size.width * [self numberOfPhotos], bounds.size.height);
  865. }
  866. - (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index {
  867. CGFloat pageWidth = _pagingScrollView.bounds.size.width;
  868. CGFloat newOffset = index * pageWidth;
  869. return CGPointMake(newOffset, 0);
  870. }
  871. - (BOOL)isLandscape:(UIInterfaceOrientation)orientation
  872. {
  873. return UIInterfaceOrientationIsLandscape(orientation);
  874. }
  875. - (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation {
  876. CGFloat height = 44;
  877. if ([self isLandscape:orientation])
  878. height = 32;
  879. return CGRectMake(0, self.view.bounds.size.height - height, self.view.bounds.size.width, height);
  880. }
  881. - (CGRect)frameForDoneButtonAtOrientation:(UIInterfaceOrientation)orientation {
  882. CGRect screenBound = self.view.bounds;
  883. CGFloat screenWidth = screenBound.size.width;
  884. // if ([self isLandscape:orientation]) screenWidth = screenBound.size.height;
  885. return CGRectMake(screenWidth - 75, 30, 55, 26);
  886. }
  887. - (CGRect)frameForCaptionView:(IDMCaptionView *)captionView atIndex:(NSUInteger)index {
  888. CGRect pageFrame = [self frameForPageAtIndex:index];
  889. CGSize captionSize = [captionView sizeThatFits:CGSizeMake(pageFrame.size.width, 0)];
  890. CGRect captionFrame = CGRectMake(pageFrame.origin.x, pageFrame.size.height - captionSize.height - (_toolbar.superview?_toolbar.frame.size.height:0), pageFrame.size.width, captionSize.height);
  891. return captionFrame;
  892. }
  893. #pragma mark - UIScrollView Delegate
  894. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  895. // Checks
  896. if (!_viewIsActive || _performingLayout || _rotating) return;
  897. // Tile pages
  898. [self tilePages];
  899. // Calculate current page
  900. CGRect visibleBounds = _pagingScrollView.bounds;
  901. NSInteger index = (NSInteger) (floorf(CGRectGetMidX(visibleBounds) / CGRectGetWidth(visibleBounds)));
  902. if (index < 0) index = 0;
  903. if (index > [self numberOfPhotos] - 1) index = [self numberOfPhotos] - 1;
  904. NSUInteger previousCurrentPage = _currentPageIndex;
  905. _currentPageIndex = index;
  906. if (_currentPageIndex != previousCurrentPage) {
  907. [self didStartViewingPageAtIndex:index];
  908. if(_arrowButtonsChangePhotosAnimated) [self updateToolbar];
  909. }
  910. }
  911. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  912. // Hide controls when dragging begins
  913. if(_autoHideInterface){
  914. [self setControlsHidden:YES animated:YES permanent:NO];
  915. }
  916. }
  917. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  918. // Update toolbar when page changes
  919. if(! _arrowButtonsChangePhotosAnimated) [self updateToolbar];
  920. }
  921. #pragma mark - Toolbar
  922. - (void)updateToolbar {
  923. // Counter
  924. if ([self numberOfPhotos] > 1) {
  925. _counterLabel.text = [NSString stringWithFormat:@"%lu %@ %lu", (unsigned long)(_currentPageIndex+1), IDMPhotoBrowserLocalizedStrings(@"of"), (unsigned long)[self numberOfPhotos]];
  926. } else {
  927. _counterLabel.text = nil;
  928. }
  929. // Buttons
  930. _previousButton.enabled = (_currentPageIndex > 0);
  931. _nextButton.enabled = (_currentPageIndex < [self numberOfPhotos]-1);
  932. }
  933. - (void)jumpToPageAtIndex:(NSUInteger)index {
  934. // Change page
  935. if (index < [self numberOfPhotos]) {
  936. CGRect pageFrame = [self frameForPageAtIndex:index];
  937. if(_arrowButtonsChangePhotosAnimated)
  938. {
  939. [_pagingScrollView setContentOffset:CGPointMake(pageFrame.origin.x - PADDING, 0) animated:YES];
  940. }
  941. else
  942. {
  943. _pagingScrollView.contentOffset = CGPointMake(pageFrame.origin.x - PADDING, 0);
  944. [self updateToolbar];
  945. }
  946. }
  947. // Update timer to give more time
  948. [self hideControlsAfterDelay];
  949. }
  950. - (void)gotoPreviousPage { [self jumpToPageAtIndex:_currentPageIndex-1]; }
  951. - (void)gotoNextPage { [self jumpToPageAtIndex:_currentPageIndex+1]; }
  952. #pragma mark - Control Hiding / Showing
  953. // If permanent then we don't set timers to hide again
  954. - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent {
  955. // Cancel any timers
  956. [self cancelControlHiding];
  957. // Captions
  958. NSMutableSet *captionViews = [[NSMutableSet alloc] initWithCapacity:_visiblePages.count];
  959. for (IDMZoomingScrollView *page in _visiblePages) {
  960. if (page.captionView) [captionViews addObject:page.captionView];
  961. }
  962. // Hide/show bars
  963. [UIView animateWithDuration:(animated ? 0.1 : 0) animations:^(void) {
  964. CGFloat alpha = hidden ? 0 : 1;
  965. [self.navigationController.navigationBar setAlpha:alpha];
  966. [_toolbar setAlpha:alpha];
  967. [_doneButton setAlpha:alpha];
  968. for (UIView *v in captionViews) v.alpha = alpha;
  969. } completion:^(BOOL finished) {}];
  970. // Control hiding timer
  971. // Will cancel existing timer but only begin hiding if they are visible
  972. if (!permanent) {
  973. [self hideControlsAfterDelay];
  974. }
  975. [self setNeedsStatusBarAppearanceUpdate];
  976. }
  977. - (void)cancelControlHiding {
  978. // If a timer exists then cancel and release
  979. if (_controlVisibilityTimer) {
  980. [_controlVisibilityTimer invalidate];
  981. _controlVisibilityTimer = nil;
  982. }
  983. }
  984. // Enable/disable control visiblity timer
  985. - (void)hideControlsAfterDelay {
  986. if (![self autoHideInterface]) {
  987. return;
  988. }
  989. if (![self areControlsHidden]) {
  990. [self cancelControlHiding];
  991. _controlVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(hideControls) userInfo:nil repeats:NO];
  992. }
  993. }
  994. - (BOOL)areControlsHidden {
  995. return (_toolbar.alpha == 0);
  996. }
  997. - (void)hideControls {
  998. if(_autoHide && _autoHideInterface) {
  999. [self setControlsHidden:YES animated:YES permanent:NO];
  1000. }
  1001. }
  1002. - (void)handleSingleTap {
  1003. if (_dismissOnTouch) {
  1004. [self doneButtonPressed:nil];
  1005. } else {
  1006. [self setControlsHidden:![self areControlsHidden] animated:YES permanent:NO];
  1007. }
  1008. }
  1009. #pragma mark - Properties
  1010. - (void)setInitialPageIndex:(NSUInteger)index {
  1011. // Validate
  1012. if (index >= [self numberOfPhotos]) index = [self numberOfPhotos]-1;
  1013. _initalPageIndex = index;
  1014. _currentPageIndex = index;
  1015. if ([self isViewLoaded]) {
  1016. [self jumpToPageAtIndex:index];
  1017. if (!_viewIsActive) [self tilePages]; // Force tiling if view is not visible
  1018. }
  1019. }
  1020. #pragma mark - Buttons
  1021. - (void)doneButtonPressed:(id)sender {
  1022. if ([_delegate respondsToSelector:@selector(willDisappearPhotoBrowser:)]) {
  1023. [_delegate willDisappearPhotoBrowser:self];
  1024. }
  1025. if (_senderViewForAnimation && _currentPageIndex == _initalPageIndex) {
  1026. IDMZoomingScrollView *scrollView = [self pageDisplayedAtIndex:_currentPageIndex];
  1027. [self performCloseAnimationWithScrollView:scrollView];
  1028. }
  1029. else {
  1030. _senderViewForAnimation.hidden = NO;
  1031. [self prepareForClosePhotoBrowser];
  1032. [self dismissPhotoBrowserAnimated:YES];
  1033. }
  1034. }
  1035. - (void)actionButtonPressed:(id)sender {
  1036. id <IDMPhoto> photo = [self photoAtIndex:_currentPageIndex];
  1037. if ([self numberOfPhotos] > 0 && [photo underlyingImage]) {
  1038. if(!_actionButtonTitles)
  1039. {
  1040. // Activity view
  1041. NSMutableArray *activityItems = [NSMutableArray arrayWithObject:[photo underlyingImage]];
  1042. if (photo.caption) [activityItems addObject:photo.caption];
  1043. self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
  1044. __typeof__(self) __weak selfBlock = self;
  1045. [self.activityViewController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
  1046. [selfBlock hideControlsAfterDelay];
  1047. selfBlock.activityViewController = nil;
  1048. }];
  1049. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1050. [self presentViewController:self.activityViewController animated:YES completion:nil];
  1051. }
  1052. else { // iPad
  1053. UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:self.activityViewController];
  1054. [popover presentPopoverFromRect:CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/4, 0, 0)
  1055. inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny
  1056. animated:YES];
  1057. }
  1058. }
  1059. else
  1060. {
  1061. // Action sheet
  1062. self.actionsSheet = [UIActionSheet new];
  1063. self.actionsSheet.delegate = self;
  1064. for(NSString *action in _actionButtonTitles) {
  1065. [self.actionsSheet addButtonWithTitle:action];
  1066. }
  1067. self.actionsSheet.cancelButtonIndex = [self.actionsSheet addButtonWithTitle:IDMPhotoBrowserLocalizedStrings(@"Cancel")];
  1068. self.actionsSheet.actionSheetStyle = UIActionSheetStyleBlackTranslucent;
  1069. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1070. [_actionsSheet showInView:self.view];
  1071. } else {
  1072. [_actionsSheet showFromBarButtonItem:sender animated:YES];
  1073. }
  1074. }
  1075. // Keep controls hidden
  1076. [self setControlsHidden:NO animated:YES permanent:YES];
  1077. }
  1078. }
  1079. #pragma mark - Action Sheet Delegate
  1080. - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
  1081. if (actionSheet == _actionsSheet) {
  1082. self.actionsSheet = nil;
  1083. if (buttonIndex != actionSheet.cancelButtonIndex) {
  1084. if ([_delegate respondsToSelector:@selector(photoBrowser:didDismissActionSheetWithButtonIndex:photoIndex:)]) {
  1085. [_delegate photoBrowser:self didDismissActionSheetWithButtonIndex:buttonIndex photoIndex:_currentPageIndex];
  1086. return;
  1087. }
  1088. }
  1089. }
  1090. [self hideControlsAfterDelay]; // Continue as normal...
  1091. }
  1092. #pragma mark - pop Animation
  1093. - (void)animateView:(UIView *)view toFrame:(CGRect)frame completion:(void (^)(void))completion
  1094. {
  1095. POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
  1096. [animation setSpringBounciness:6];
  1097. [animation setDynamicsMass:1];
  1098. [animation setToValue:[NSValue valueWithCGRect:frame]];
  1099. [view pop_addAnimation:animation forKey:nil];
  1100. if (completion)
  1101. {
  1102. [animation setCompletionBlock:^(POPAnimation *animation, BOOL finished) {
  1103. completion();
  1104. }];
  1105. }
  1106. }
  1107. @end