IDMPhotoBrowser.m 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392
  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. // view.bounds.size
  428. UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), YES, 2);
  429. [view.layer renderInContext:UIGraphicsGetCurrentContext()];
  430. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  431. UIGraphicsEndImageContext();
  432. // CGSize size = view.bounds.size;
  433. // UIGraphicsImageRendererFormat *format = [[UIGraphicsImageRendererFormat alloc] init];
  434. // format.scale = [UIScreen mainScreen].scale;
  435. // UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:format];
  436. // // 绘制图像
  437. // UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
  438. // [view.layer renderInContext:rendererContext.CGContext];
  439. // }];
  440. return image;
  441. }
  442. - (UIViewController *)topviewController
  443. {
  444. UIViewController *topviewController = [UIApplication sharedApplication].keyWindow.rootViewController;
  445. while (topviewController.presentedViewController) {
  446. topviewController = topviewController.presentedViewController;
  447. }
  448. return topviewController;
  449. }
  450. #pragma mark - View Lifecycle
  451. - (void)viewDidLoad {
  452. // View
  453. self.view.backgroundColor = [UIColor colorWithWhite:(_useWhiteBackgroundColor ? 1 : 0) alpha:1];
  454. self.view.clipsToBounds = YES;
  455. // Setup paging scrolling view
  456. CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
  457. _pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
  458. //_pagingScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  459. _pagingScrollView.pagingEnabled = YES;
  460. _pagingScrollView.delegate = self;
  461. _pagingScrollView.showsHorizontalScrollIndicator = NO;
  462. _pagingScrollView.showsVerticalScrollIndicator = NO;
  463. _pagingScrollView.backgroundColor = [UIColor clearColor];
  464. _pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
  465. [self.view addSubview:_pagingScrollView];
  466. // Transition animation
  467. [self performPresentAnimation];
  468. UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;
  469. // Toolbar
  470. _toolbar = [[UIToolbar alloc] initWithFrame:[self frameForToolbarAtOrientation:currentOrientation]];
  471. _toolbar.backgroundColor = [UIColor clearColor];
  472. _toolbar.clipsToBounds = YES;
  473. _toolbar.translucent = YES;
  474. [_toolbar setBackgroundImage:[UIImage new]
  475. forToolbarPosition:UIToolbarPositionAny
  476. barMetrics:UIBarMetricsDefault];
  477. // Close Button
  478. _doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
  479. [_doneButton setFrame:[self frameForDoneButtonAtOrientation:currentOrientation]];
  480. [_doneButton setAlpha:1.0f];
  481. [_doneButton addTarget:self action:@selector(doneButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
  482. if(!_doneButtonImage) {
  483. [_doneButton setTitleColor:[UIColor colorWithWhite:0.9 alpha:0.9] forState:UIControlStateNormal|UIControlStateHighlighted];
  484. [_doneButton setTitle:IDMPhotoBrowserLocalizedStrings(@"Done") forState:UIControlStateNormal];
  485. [_doneButton.titleLabel setFont:[UIFont boldSystemFontOfSize:11.0f]];
  486. [_doneButton setBackgroundColor:[UIColor colorWithWhite:0.1 alpha:0.5]];
  487. _doneButton.layer.cornerRadius = 3.0f;
  488. _doneButton.layer.borderColor = [UIColor colorWithWhite:0.9 alpha:0.9].CGColor;
  489. _doneButton.layer.borderWidth = 1.0f;
  490. }
  491. else {
  492. [_doneButton setImage:_doneButtonImage forState:UIControlStateNormal];
  493. _doneButton.contentMode = UIViewContentModeScaleAspectFit;
  494. }
  495. UIImage *leftButtonImage = (_leftArrowImage == nil) ?
  496. [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeft.png"] : _leftArrowImage;
  497. UIImage *rightButtonImage = (_rightArrowImage == nil) ?
  498. [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRight.png"] : _rightArrowImage;
  499. UIImage *leftButtonSelectedImage = (_leftArrowSelectedImage == nil) ?
  500. [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowLeftSelected.png"] : _leftArrowSelectedImage;
  501. UIImage *rightButtonSelectedImage = (_rightArrowSelectedImage == nil) ?
  502. [UIImage imageNamed:@"IDMPhotoBrowser.bundle/images/IDMPhotoBrowser_arrowRightSelected.png"] : _rightArrowSelectedImage;
  503. // Arrows
  504. _previousButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:leftButtonImage
  505. imageSelected:leftButtonSelectedImage
  506. action:@selector(gotoPreviousPage)]];
  507. _nextButton = [[UIBarButtonItem alloc] initWithCustomView:[self customToolbarButtonImage:rightButtonImage
  508. imageSelected:rightButtonSelectedImage
  509. action:@selector(gotoNextPage)]];
  510. // Counter Label
  511. _counterLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 95, 40)];
  512. _counterLabel.textAlignment = NSTextAlignmentCenter;
  513. _counterLabel.backgroundColor = [UIColor clearColor];
  514. _counterLabel.font = [UIFont fontWithName:@"Helvetica" size:17];
  515. if(_useWhiteBackgroundColor == NO) {
  516. _counterLabel.textColor = [UIColor whiteColor];
  517. _counterLabel.shadowColor = [UIColor darkTextColor];
  518. _counterLabel.shadowOffset = CGSizeMake(0, 1);
  519. }
  520. else {
  521. _counterLabel.textColor = [UIColor blackColor];
  522. }
  523. // Counter Button
  524. _counterButton = [[UIBarButtonItem alloc] initWithCustomView:_counterLabel];
  525. // Action Button
  526. _actionButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction
  527. target:self
  528. action:@selector(actionButtonPressed:)];
  529. // Gesture
  530. _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognized:)];
  531. [_panGesture setMinimumNumberOfTouches:1];
  532. [_panGesture setMaximumNumberOfTouches:1];
  533. // Update
  534. //[self reloadData];
  535. // Super
  536. [super viewDidLoad];
  537. }
  538. - (void)viewWillAppear:(BOOL)animated {
  539. // Update
  540. [self reloadData];
  541. if ([_delegate respondsToSelector:@selector(willAppearPhotoBrowser:)]) {
  542. [_delegate willAppearPhotoBrowser:self];
  543. }
  544. // Super
  545. [super viewWillAppear:animated];
  546. // Status Bar
  547. _statusBarOriginallyHidden = [UIApplication sharedApplication].statusBarHidden;
  548. // Update UI
  549. [self hideControlsAfterDelay];
  550. }
  551. - (void)viewDidAppear:(BOOL)animated {
  552. [super viewDidAppear:animated];
  553. _viewIsActive = YES;
  554. }
  555. // Release any retained subviews of the main view.
  556. - (void)viewDidUnload {
  557. _currentPageIndex = 0;
  558. _pagingScrollView = nil;
  559. _visiblePages = nil;
  560. _recycledPages = nil;
  561. _toolbar = nil;
  562. _doneButton = nil;
  563. _previousButton = nil;
  564. _nextButton = nil;
  565. [super viewDidUnload];
  566. }
  567. #pragma mark - Status Bar
  568. - (UIStatusBarStyle)preferredStatusBarStyle {
  569. return _useWhiteBackgroundColor ? UIStatusBarStyleDefault : UIStatusBarStyleLightContent;
  570. }
  571. - (BOOL)prefersStatusBarHidden {
  572. if(_forceHideStatusBar) {
  573. return YES;
  574. }
  575. if(_isdraggingPhoto) {
  576. if(_statusBarOriginallyHidden) {
  577. return YES;
  578. }
  579. else {
  580. return NO;
  581. }
  582. }
  583. else {
  584. return [self areControlsHidden];
  585. }
  586. }
  587. - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
  588. return UIStatusBarAnimationFade;
  589. }
  590. #pragma mark - Layout
  591. - (void)viewWillLayoutSubviews {
  592. // Flag
  593. _performingLayout = YES;
  594. UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation;
  595. // Toolbar
  596. _toolbar.frame = [self frameForToolbarAtOrientation:currentOrientation];
  597. // Done button
  598. _doneButton.frame = [self frameForDoneButtonAtOrientation:currentOrientation];
  599. // Remember index
  600. NSUInteger indexPriorToLayout = _currentPageIndex;
  601. // Get paging scroll view frame to determine if anything needs changing
  602. CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
  603. // Frame needs changing
  604. _pagingScrollView.frame = pagingScrollViewFrame;
  605. // Recalculate contentSize based on current orientation
  606. _pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
  607. // Adjust frames and configuration of each visible page
  608. for (IDMZoomingScrollView *page in _visiblePages) {
  609. NSUInteger index = PAGE_INDEX(page);
  610. page.frame = [self frameForPageAtIndex:index];
  611. page.captionView.frame = [self frameForCaptionView:page.captionView atIndex:index];
  612. [page setMaxMinZoomScalesForCurrentBounds];
  613. }
  614. // Adjust contentOffset to preserve page location based on values collected prior to location
  615. _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:indexPriorToLayout];
  616. [self didStartViewingPageAtIndex:_currentPageIndex]; // initial
  617. // Reset
  618. _currentPageIndex = indexPriorToLayout;
  619. _performingLayout = NO;
  620. // Super
  621. [super viewWillLayoutSubviews];
  622. }
  623. - (void)performLayout {
  624. // Setup
  625. _performingLayout = YES;
  626. NSUInteger numberOfPhotos = [self numberOfPhotos];
  627. // Setup pages
  628. [_visiblePages removeAllObjects];
  629. [_recycledPages removeAllObjects];
  630. // Toolbar
  631. if (_displayToolbar) {
  632. [self.view addSubview:_toolbar];
  633. } else {
  634. [_toolbar removeFromSuperview];
  635. }
  636. // Close button
  637. if(_displayDoneButton && !self.navigationController.navigationBar)
  638. [self.view addSubview:_doneButton];
  639. // Toolbar items & navigation
  640. UIBarButtonItem *fixedLeftSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
  641. target:self action:nil];
  642. fixedLeftSpace.width = 32; // To balance action button
  643. UIBarButtonItem *flexSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
  644. target:self action:nil];
  645. NSMutableArray *items = [NSMutableArray new];
  646. if (_displayActionButton)
  647. [items addObject:fixedLeftSpace];
  648. [items addObject:flexSpace];
  649. if (numberOfPhotos > 1 && _displayArrowButton)
  650. [items addObject:_previousButton];
  651. if(_displayCounterLabel) {
  652. [items addObject:flexSpace];
  653. [items addObject:_counterButton];
  654. }
  655. [items addObject:flexSpace];
  656. if (numberOfPhotos > 1 && _displayArrowButton)
  657. [items addObject:_nextButton];
  658. [items addObject:flexSpace];
  659. if(_displayActionButton)
  660. [items addObject:_actionButton];
  661. [_toolbar setItems:items];
  662. [self updateToolbar];
  663. // Content offset
  664. _pagingScrollView.contentOffset = [self contentOffsetForPageAtIndex:_currentPageIndex];
  665. [self tilePages];
  666. _performingLayout = NO;
  667. if(! _disableVerticalSwipe) {
  668. [self.view addGestureRecognizer:_panGesture];
  669. }
  670. }
  671. #pragma mark - Data
  672. - (void)reloadData {
  673. // Get data
  674. [self releaseAllUnderlyingPhotos];
  675. // Update
  676. [self performLayout];
  677. // Layout
  678. [self.view setNeedsLayout];
  679. }
  680. - (NSUInteger)numberOfPhotos {
  681. return _photos.count;
  682. }
  683. - (id<IDMPhoto>)photoAtIndex:(NSUInteger)index {
  684. return _photos[index];
  685. }
  686. - (IDMCaptionView *)captionViewForPhotoAtIndex:(NSUInteger)index {
  687. IDMCaptionView *captionView = nil;
  688. if ([_delegate respondsToSelector:@selector(photoBrowser:captionViewForPhotoAtIndex:)]) {
  689. captionView = [_delegate photoBrowser:self captionViewForPhotoAtIndex:index];
  690. } else {
  691. id <IDMPhoto> photo = [self photoAtIndex:index];
  692. if ([photo respondsToSelector:@selector(caption)]) {
  693. if ([photo caption]) captionView = [[IDMCaptionView alloc] initWithPhoto:photo];
  694. }
  695. }
  696. captionView.alpha = [self areControlsHidden] ? 0 : 1; // Initial alpha
  697. return captionView;
  698. }
  699. - (UIImage *)imageForPhoto:(id<IDMPhoto>)photo {
  700. if (photo) {
  701. // Get image or obtain in background
  702. if ([photo underlyingImage]) {
  703. return [photo underlyingImage];
  704. } else {
  705. [photo loadUnderlyingImageAndNotify];
  706. if ([photo respondsToSelector:@selector(placeholderImage)]) {
  707. return [photo placeholderImage];
  708. }
  709. }
  710. }
  711. return nil;
  712. }
  713. - (void)loadAdjacentPhotosIfNecessary:(id<IDMPhoto>)photo {
  714. IDMZoomingScrollView *page = [self pageDisplayingPhoto:photo];
  715. if (page) {
  716. // If page is current page then initiate loading of previous and next pages
  717. NSUInteger pageIndex = PAGE_INDEX(page);
  718. if (_currentPageIndex == pageIndex) {
  719. if (pageIndex > 0) {
  720. // Preload index - 1
  721. id <IDMPhoto> photo = [self photoAtIndex:pageIndex-1];
  722. if (![photo underlyingImage]) {
  723. [photo loadUnderlyingImageAndNotify];
  724. IDMLog(@"Pre-loading image at index %i", pageIndex-1);
  725. }
  726. }
  727. if (pageIndex < [self numberOfPhotos] - 1) {
  728. // Preload index + 1
  729. id <IDMPhoto> photo = [self photoAtIndex:pageIndex+1];
  730. if (![photo underlyingImage]) {
  731. [photo loadUnderlyingImageAndNotify];
  732. IDMLog(@"Pre-loading image at index %i", pageIndex+1);
  733. }
  734. }
  735. }
  736. }
  737. }
  738. #pragma mark - IDMPhoto Loading Notification
  739. - (void)handleIDMPhotoLoadingDidEndNotification:(NSNotification *)notification {
  740. id <IDMPhoto> photo = [notification object];
  741. IDMZoomingScrollView *page = [self pageDisplayingPhoto:photo];
  742. if (page) {
  743. if ([photo underlyingImage]) {
  744. // Successful load
  745. [page displayImage];
  746. [self loadAdjacentPhotosIfNecessary:photo];
  747. } else {
  748. // Failed to load
  749. [page displayImageFailure];
  750. }
  751. }
  752. }
  753. #pragma mark - Paging
  754. - (void)tilePages {
  755. // Calculate which pages should be visible
  756. // Ignore padding as paging bounces encroach on that
  757. // and lead to false page loads
  758. CGRect visibleBounds = _pagingScrollView.bounds;
  759. NSInteger iFirstIndex = (NSInteger) floorf((CGRectGetMinX(visibleBounds)+PADDING*2) / CGRectGetWidth(visibleBounds));
  760. NSInteger iLastIndex = (NSInteger) floorf((CGRectGetMaxX(visibleBounds)-PADDING*2-1) / CGRectGetWidth(visibleBounds));
  761. if (iFirstIndex < 0) iFirstIndex = 0;
  762. if (iFirstIndex > [self numberOfPhotos] - 1) iFirstIndex = [self numberOfPhotos] - 1;
  763. if (iLastIndex < 0) iLastIndex = 0;
  764. if (iLastIndex > [self numberOfPhotos] - 1) iLastIndex = [self numberOfPhotos] - 1;
  765. // Recycle no longer needed pages
  766. NSInteger pageIndex;
  767. for (IDMZoomingScrollView *page in _visiblePages) {
  768. pageIndex = PAGE_INDEX(page);
  769. if (pageIndex < (NSUInteger)iFirstIndex || pageIndex > (NSUInteger)iLastIndex) {
  770. [_recycledPages addObject:page];
  771. [page prepareForReuse];
  772. [page removeFromSuperview];
  773. IDMLog(@"Removed page at index %i", PAGE_INDEX(page));
  774. }
  775. }
  776. [_visiblePages minusSet:_recycledPages];
  777. while (_recycledPages.count > 2) // Only keep 2 recycled pages
  778. [_recycledPages removeObject:[_recycledPages anyObject]];
  779. // Add missing pages
  780. for (NSUInteger index = (NSUInteger)iFirstIndex; index <= (NSUInteger)iLastIndex; index++) {
  781. if (![self isDisplayingPageForIndex:index]) {
  782. // Add new page
  783. IDMZoomingScrollView *page;
  784. page = [[IDMZoomingScrollView alloc] initWithPhotoBrowser:self];
  785. page.backgroundColor = [UIColor clearColor];
  786. page.opaque = YES;
  787. [self configurePage:page forIndex:index];
  788. [_visiblePages addObject:page];
  789. [_pagingScrollView addSubview:page];
  790. IDMLog(@"Added page at index %i", index);
  791. // Add caption
  792. IDMCaptionView *captionView = [self captionViewForPhotoAtIndex:index];
  793. captionView.frame = [self frameForCaptionView:captionView atIndex:index];
  794. [_pagingScrollView addSubview:captionView];
  795. page.captionView = captionView;
  796. }
  797. }
  798. }
  799. - (BOOL)isDisplayingPageForIndex:(NSUInteger)index {
  800. for (IDMZoomingScrollView *page in _visiblePages)
  801. if (PAGE_INDEX(page) == index) return YES;
  802. return NO;
  803. }
  804. - (IDMZoomingScrollView *)pageDisplayedAtIndex:(NSUInteger)index {
  805. IDMZoomingScrollView *thePage = nil;
  806. for (IDMZoomingScrollView *page in _visiblePages) {
  807. if (PAGE_INDEX(page) == index) {
  808. thePage = page; break;
  809. }
  810. }
  811. return thePage;
  812. }
  813. - (IDMZoomingScrollView *)pageDisplayingPhoto:(id<IDMPhoto>)photo {
  814. IDMZoomingScrollView *thePage = nil;
  815. for (IDMZoomingScrollView *page in _visiblePages) {
  816. if (page.photo == photo) {
  817. thePage = page; break;
  818. }
  819. }
  820. return thePage;
  821. }
  822. - (void)configurePage:(IDMZoomingScrollView *)page forIndex:(NSUInteger)index {
  823. page.frame = [self frameForPageAtIndex:index];
  824. page.tag = PAGE_INDEX_TAG_OFFSET + index;
  825. page.photo = [self photoAtIndex:index];
  826. __block __weak IDMPhoto *photo = (IDMPhoto*)page.photo;
  827. __weak IDMZoomingScrollView* weakPage = page;
  828. photo.progressUpdateBlock = ^(CGFloat progress){
  829. [weakPage setProgress:progress forPhoto:photo];
  830. };
  831. }
  832. - (IDMZoomingScrollView *)dequeueRecycledPage {
  833. IDMZoomingScrollView *page = [_recycledPages anyObject];
  834. if (page) {
  835. [_recycledPages removeObject:page];
  836. }
  837. return page;
  838. }
  839. // Handle page changes
  840. - (void)didStartViewingPageAtIndex:(NSUInteger)index {
  841. // Load adjacent images if needed and the photo is already
  842. // loaded. Also called after photo has been loaded in background
  843. id <IDMPhoto> currentPhoto = [self photoAtIndex:index];
  844. if ([currentPhoto underlyingImage]) {
  845. // photo loaded so load ajacent now
  846. [self loadAdjacentPhotosIfNecessary:currentPhoto];
  847. }
  848. if ([_delegate respondsToSelector:@selector(photoBrowser:didShowPhotoAtIndex:)]) {
  849. [_delegate photoBrowser:self didShowPhotoAtIndex:index];
  850. }
  851. }
  852. #pragma mark - Frame Calculations
  853. - (CGRect)frameForPagingScrollView {
  854. CGRect frame = self.view.bounds;
  855. frame.origin.x -= PADDING;
  856. frame.size.width += (2 * PADDING);
  857. return frame;
  858. }
  859. - (CGRect)frameForPageAtIndex:(NSUInteger)index {
  860. // We have to use our paging scroll view's bounds, not frame, to calculate the page placement. When the device is in
  861. // landscape orientation, the frame will still be in portrait because the pagingScrollView is the root view controller's
  862. // view, so its frame is in window coordinate space, which is never rotated. Its bounds, however, will be in landscape
  863. // because it has a rotation transform applied.
  864. CGRect bounds = _pagingScrollView.bounds;
  865. CGRect pageFrame = bounds;
  866. pageFrame.size.width -= (2 * PADDING);
  867. pageFrame.origin.x = (bounds.size.width * index) + PADDING;
  868. return pageFrame;
  869. }
  870. - (CGSize)contentSizeForPagingScrollView {
  871. // We have to use the paging scroll view's bounds to calculate the contentSize, for the same reason outlined above.
  872. CGRect bounds = _pagingScrollView.bounds;
  873. return CGSizeMake(bounds.size.width * [self numberOfPhotos], bounds.size.height);
  874. }
  875. - (CGPoint)contentOffsetForPageAtIndex:(NSUInteger)index {
  876. CGFloat pageWidth = _pagingScrollView.bounds.size.width;
  877. CGFloat newOffset = index * pageWidth;
  878. return CGPointMake(newOffset, 0);
  879. }
  880. - (BOOL)isLandscape:(UIInterfaceOrientation)orientation
  881. {
  882. return UIInterfaceOrientationIsLandscape(orientation);
  883. }
  884. - (CGRect)frameForToolbarAtOrientation:(UIInterfaceOrientation)orientation {
  885. CGFloat height = 44;
  886. if ([self isLandscape:orientation])
  887. height = 32;
  888. return CGRectMake(0, self.view.bounds.size.height - height, self.view.bounds.size.width, height);
  889. }
  890. - (CGRect)frameForDoneButtonAtOrientation:(UIInterfaceOrientation)orientation {
  891. CGRect screenBound = self.view.bounds;
  892. CGFloat screenWidth = screenBound.size.width;
  893. // if ([self isLandscape:orientation]) screenWidth = screenBound.size.height;
  894. return CGRectMake(screenWidth - 75, 30, 55, 26);
  895. }
  896. - (CGRect)frameForCaptionView:(IDMCaptionView *)captionView atIndex:(NSUInteger)index {
  897. CGRect pageFrame = [self frameForPageAtIndex:index];
  898. CGSize captionSize = [captionView sizeThatFits:CGSizeMake(pageFrame.size.width, 0)];
  899. CGRect captionFrame = CGRectMake(pageFrame.origin.x, pageFrame.size.height - captionSize.height - (_toolbar.superview?_toolbar.frame.size.height:0), pageFrame.size.width, captionSize.height);
  900. return captionFrame;
  901. }
  902. #pragma mark - UIScrollView Delegate
  903. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  904. // Checks
  905. if (!_viewIsActive || _performingLayout || _rotating) return;
  906. // Tile pages
  907. [self tilePages];
  908. // Calculate current page
  909. CGRect visibleBounds = _pagingScrollView.bounds;
  910. NSInteger index = (NSInteger) (floorf(CGRectGetMidX(visibleBounds) / CGRectGetWidth(visibleBounds)));
  911. if (index < 0) index = 0;
  912. if (index > [self numberOfPhotos] - 1) index = [self numberOfPhotos] - 1;
  913. NSUInteger previousCurrentPage = _currentPageIndex;
  914. _currentPageIndex = index;
  915. if (_currentPageIndex != previousCurrentPage) {
  916. [self didStartViewingPageAtIndex:index];
  917. if(_arrowButtonsChangePhotosAnimated) [self updateToolbar];
  918. }
  919. }
  920. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  921. // Hide controls when dragging begins
  922. if(_autoHideInterface){
  923. [self setControlsHidden:YES animated:YES permanent:NO];
  924. }
  925. }
  926. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  927. // Update toolbar when page changes
  928. if(! _arrowButtonsChangePhotosAnimated) [self updateToolbar];
  929. }
  930. #pragma mark - Toolbar
  931. - (void)updateToolbar {
  932. // Counter
  933. if ([self numberOfPhotos] > 1) {
  934. _counterLabel.text = [NSString stringWithFormat:@"%lu %@ %lu", (unsigned long)(_currentPageIndex+1), IDMPhotoBrowserLocalizedStrings(@"of"), (unsigned long)[self numberOfPhotos]];
  935. } else {
  936. _counterLabel.text = nil;
  937. }
  938. // Buttons
  939. _previousButton.enabled = (_currentPageIndex > 0);
  940. _nextButton.enabled = (_currentPageIndex < [self numberOfPhotos]-1);
  941. }
  942. - (void)jumpToPageAtIndex:(NSUInteger)index {
  943. // Change page
  944. if (index < [self numberOfPhotos]) {
  945. CGRect pageFrame = [self frameForPageAtIndex:index];
  946. if(_arrowButtonsChangePhotosAnimated)
  947. {
  948. [_pagingScrollView setContentOffset:CGPointMake(pageFrame.origin.x - PADDING, 0) animated:YES];
  949. }
  950. else
  951. {
  952. _pagingScrollView.contentOffset = CGPointMake(pageFrame.origin.x - PADDING, 0);
  953. [self updateToolbar];
  954. }
  955. }
  956. // Update timer to give more time
  957. [self hideControlsAfterDelay];
  958. }
  959. - (void)gotoPreviousPage { [self jumpToPageAtIndex:_currentPageIndex-1]; }
  960. - (void)gotoNextPage { [self jumpToPageAtIndex:_currentPageIndex+1]; }
  961. #pragma mark - Control Hiding / Showing
  962. // If permanent then we don't set timers to hide again
  963. - (void)setControlsHidden:(BOOL)hidden animated:(BOOL)animated permanent:(BOOL)permanent {
  964. // Cancel any timers
  965. [self cancelControlHiding];
  966. // Captions
  967. NSMutableSet *captionViews = [[NSMutableSet alloc] initWithCapacity:_visiblePages.count];
  968. for (IDMZoomingScrollView *page in _visiblePages) {
  969. if (page.captionView) [captionViews addObject:page.captionView];
  970. }
  971. // Hide/show bars
  972. [UIView animateWithDuration:(animated ? 0.1 : 0) animations:^(void) {
  973. CGFloat alpha = hidden ? 0 : 1;
  974. [self.navigationController.navigationBar setAlpha:alpha];
  975. [_toolbar setAlpha:alpha];
  976. [_doneButton setAlpha:alpha];
  977. for (UIView *v in captionViews) v.alpha = alpha;
  978. } completion:^(BOOL finished) {}];
  979. // Control hiding timer
  980. // Will cancel existing timer but only begin hiding if they are visible
  981. if (!permanent) {
  982. [self hideControlsAfterDelay];
  983. }
  984. [self setNeedsStatusBarAppearanceUpdate];
  985. }
  986. - (void)cancelControlHiding {
  987. // If a timer exists then cancel and release
  988. if (_controlVisibilityTimer) {
  989. [_controlVisibilityTimer invalidate];
  990. _controlVisibilityTimer = nil;
  991. }
  992. }
  993. // Enable/disable control visiblity timer
  994. - (void)hideControlsAfterDelay {
  995. if (![self autoHideInterface]) {
  996. return;
  997. }
  998. if (![self areControlsHidden]) {
  999. [self cancelControlHiding];
  1000. _controlVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(hideControls) userInfo:nil repeats:NO];
  1001. }
  1002. }
  1003. - (BOOL)areControlsHidden {
  1004. return (_toolbar.alpha == 0);
  1005. }
  1006. - (void)hideControls {
  1007. if(_autoHide && _autoHideInterface) {
  1008. [self setControlsHidden:YES animated:YES permanent:NO];
  1009. }
  1010. }
  1011. - (void)handleSingleTap {
  1012. if (_dismissOnTouch) {
  1013. [self doneButtonPressed:nil];
  1014. } else {
  1015. [self setControlsHidden:![self areControlsHidden] animated:YES permanent:NO];
  1016. }
  1017. }
  1018. #pragma mark - Properties
  1019. - (void)setInitialPageIndex:(NSUInteger)index {
  1020. // Validate
  1021. if (index >= [self numberOfPhotos]) index = [self numberOfPhotos]-1;
  1022. _initalPageIndex = index;
  1023. _currentPageIndex = index;
  1024. if ([self isViewLoaded]) {
  1025. [self jumpToPageAtIndex:index];
  1026. if (!_viewIsActive) [self tilePages]; // Force tiling if view is not visible
  1027. }
  1028. }
  1029. #pragma mark - Buttons
  1030. - (void)doneButtonPressed:(id)sender {
  1031. if ([_delegate respondsToSelector:@selector(willDisappearPhotoBrowser:)]) {
  1032. [_delegate willDisappearPhotoBrowser:self];
  1033. }
  1034. if (_senderViewForAnimation && _currentPageIndex == _initalPageIndex) {
  1035. IDMZoomingScrollView *scrollView = [self pageDisplayedAtIndex:_currentPageIndex];
  1036. [self performCloseAnimationWithScrollView:scrollView];
  1037. }
  1038. else {
  1039. _senderViewForAnimation.hidden = NO;
  1040. [self prepareForClosePhotoBrowser];
  1041. [self dismissPhotoBrowserAnimated:YES];
  1042. }
  1043. }
  1044. - (void)actionButtonPressed:(id)sender {
  1045. id <IDMPhoto> photo = [self photoAtIndex:_currentPageIndex];
  1046. if ([self numberOfPhotos] > 0 && [photo underlyingImage]) {
  1047. if(!_actionButtonTitles)
  1048. {
  1049. // Activity view
  1050. NSMutableArray *activityItems = [NSMutableArray arrayWithObject:[photo underlyingImage]];
  1051. if (photo.caption) [activityItems addObject:photo.caption];
  1052. self.activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
  1053. __typeof__(self) __weak selfBlock = self;
  1054. [self.activityViewController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
  1055. [selfBlock hideControlsAfterDelay];
  1056. selfBlock.activityViewController = nil;
  1057. }];
  1058. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1059. [self presentViewController:self.activityViewController animated:YES completion:nil];
  1060. }
  1061. else { // iPad
  1062. UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:self.activityViewController];
  1063. [popover presentPopoverFromRect:CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/4, 0, 0)
  1064. inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny
  1065. animated:YES];
  1066. }
  1067. }
  1068. else
  1069. {
  1070. // Action sheet
  1071. self.actionsSheet = [UIActionSheet new];
  1072. self.actionsSheet.delegate = self;
  1073. for(NSString *action in _actionButtonTitles) {
  1074. [self.actionsSheet addButtonWithTitle:action];
  1075. }
  1076. self.actionsSheet.cancelButtonIndex = [self.actionsSheet addButtonWithTitle:IDMPhotoBrowserLocalizedStrings(@"Cancel")];
  1077. self.actionsSheet.actionSheetStyle = UIActionSheetStyleBlackTranslucent;
  1078. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
  1079. [_actionsSheet showInView:self.view];
  1080. } else {
  1081. [_actionsSheet showFromBarButtonItem:sender animated:YES];
  1082. }
  1083. }
  1084. // Keep controls hidden
  1085. [self setControlsHidden:NO animated:YES permanent:YES];
  1086. }
  1087. }
  1088. #pragma mark - Action Sheet Delegate
  1089. - (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
  1090. if (actionSheet == _actionsSheet) {
  1091. self.actionsSheet = nil;
  1092. if (buttonIndex != actionSheet.cancelButtonIndex) {
  1093. if ([_delegate respondsToSelector:@selector(photoBrowser:didDismissActionSheetWithButtonIndex:photoIndex:)]) {
  1094. [_delegate photoBrowser:self didDismissActionSheetWithButtonIndex:buttonIndex photoIndex:_currentPageIndex];
  1095. return;
  1096. }
  1097. }
  1098. }
  1099. [self hideControlsAfterDelay]; // Continue as normal...
  1100. }
  1101. #pragma mark - pop Animation
  1102. - (void)animateView:(UIView *)view toFrame:(CGRect)frame completion:(void (^)(void))completion
  1103. {
  1104. POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
  1105. [animation setSpringBounciness:6];
  1106. [animation setDynamicsMass:1];
  1107. [animation setToValue:[NSValue valueWithCGRect:frame]];
  1108. [view pop_addAnimation:animation forKey:nil];
  1109. if (completion)
  1110. {
  1111. [animation setCompletionBlock:^(POPAnimation *animation, BOOL finished) {
  1112. completion();
  1113. }];
  1114. }
  1115. }
  1116. @end