YYPhotoGroupView.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. //
  2. // YYPhotoGroupView.m
  3. //
  4. // Created by ibireme on 14/3/9.
  5. // Copyright (C) 2014 ibireme. All rights reserved.
  6. //
  7. #import "YYPhotoGroupView.h"
  8. #import "YYKit.h"
  9. #define kPadding 20
  10. #define kHiColor [UIColor colorWithRGBHex:0x2dd6b8]
  11. @interface YYPhotoGroupItem()<NSCopying>
  12. @property (nonatomic, readonly) UIImage *thumbImage;
  13. @property (nonatomic, readonly) BOOL thumbClippedToTop;
  14. - (BOOL)shouldClipToTop:(CGSize)imageSize forView:(UIView *)view;
  15. @end
  16. @implementation YYPhotoGroupItem
  17. - (UIImage *)thumbImage {
  18. if ([_thumbView respondsToSelector:@selector(image)]) {
  19. return ((UIImageView *)_thumbView).image;
  20. }
  21. return nil;
  22. }
  23. - (BOOL)thumbClippedToTop {
  24. if (_thumbView) {
  25. if (_thumbView.layer.contentsRect.size.height < 1) {
  26. return YES;
  27. }
  28. }
  29. return NO;
  30. }
  31. - (BOOL)shouldClipToTop:(CGSize)imageSize forView:(UIView *)view {
  32. if (imageSize.width < 1 || imageSize.height < 1) return NO;
  33. if (view.width < 1 || view.height < 1) return NO;
  34. return imageSize.height / imageSize.width > view.width / view.height;
  35. }
  36. - (id)copyWithZone:(NSZone *)zone {
  37. YYPhotoGroupItem *item = [self.class new];
  38. return item;
  39. }
  40. @end
  41. @interface YYPhotoGroupCell : UIScrollView <UIScrollViewDelegate>
  42. @property (nonatomic, strong) UIView *imageContainerView;
  43. @property (nonatomic, strong) YYAnimatedImageView *imageView;
  44. @property (nonatomic, assign) NSInteger page;
  45. @property (nonatomic, assign) BOOL showProgress;
  46. @property (nonatomic, assign) CGFloat progress;
  47. @property (nonatomic, strong) CAShapeLayer *progressLayer;
  48. @property (nonatomic, strong) YYPhotoGroupItem *item;
  49. @property (nonatomic, readonly) BOOL itemDidLoad;
  50. - (void)resizeSubviewSize;
  51. @end
  52. @implementation YYPhotoGroupCell
  53. - (instancetype)init {
  54. self = super.init;
  55. if (!self) return nil;
  56. self.delegate = self;
  57. self.bouncesZoom = YES;
  58. self.maximumZoomScale = 3;
  59. self.multipleTouchEnabled = YES;
  60. self.alwaysBounceVertical = NO;
  61. self.showsVerticalScrollIndicator = YES;
  62. self.showsHorizontalScrollIndicator = NO;
  63. self.frame = [UIScreen mainScreen].bounds;
  64. _imageContainerView = [UIView new];
  65. _imageContainerView.clipsToBounds = YES;
  66. [self addSubview:_imageContainerView];
  67. _imageView = [YYAnimatedImageView new];
  68. _imageView.clipsToBounds = YES;
  69. _imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500];
  70. [_imageContainerView addSubview:_imageView];
  71. _progressLayer = [CAShapeLayer layer];
  72. _progressLayer.size = CGSizeMake(40, 40);
  73. _progressLayer.cornerRadius = 20;
  74. _progressLayer.backgroundColor = [UIColor colorWithWhite:0.000 alpha:0.500].CGColor;
  75. UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(_progressLayer.bounds, 7, 7) cornerRadius:(40 / 2 - 7)];
  76. _progressLayer.path = path.CGPath;
  77. _progressLayer.fillColor = [UIColor clearColor].CGColor;
  78. _progressLayer.strokeColor = [UIColor whiteColor].CGColor;
  79. _progressLayer.lineWidth = 4;
  80. _progressLayer.lineCap = kCALineCapRound;
  81. _progressLayer.strokeStart = 0;
  82. _progressLayer.strokeEnd = 0;
  83. _progressLayer.hidden = YES;
  84. [self.layer addSublayer:_progressLayer];
  85. return self;
  86. }
  87. - (void)layoutSubviews {
  88. [super layoutSubviews];
  89. _progressLayer.center = CGPointMake(self.width / 2, self.height / 2);
  90. }
  91. - (void)setItem:(YYPhotoGroupItem *)item {
  92. if (_item == item) return;
  93. _item = item;
  94. _itemDidLoad = NO;
  95. [self setZoomScale:1.0 animated:NO];
  96. self.maximumZoomScale = 1;
  97. [_imageView cancelCurrentImageRequest];
  98. [_imageView.layer removePreviousFadeAnimation];
  99. _progressLayer.hidden = NO;
  100. [CATransaction begin];
  101. [CATransaction setDisableActions:YES];
  102. _progressLayer.strokeEnd = 0;
  103. _progressLayer.hidden = YES;
  104. [CATransaction commit];
  105. if (!_item) {
  106. _imageView.image = nil;
  107. return;
  108. }
  109. @weakify(self);
  110. [_imageView setImageWithURL:item.largeImageURL placeholder:item.thumbImage options:kNilOptions progress:^(NSInteger receivedSize, NSInteger expectedSize) {
  111. @strongify(self);
  112. if (!self) return;
  113. CGFloat progress = receivedSize / (float)expectedSize;
  114. progress = progress < 0.01 ? 0.01 : progress > 1 ? 1 : progress;
  115. if (isnan(progress)) progress = 0;
  116. self.progressLayer.hidden = NO;
  117. self.progressLayer.strokeEnd = progress;
  118. } transform:nil completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
  119. @strongify(self);
  120. if (!self) return;
  121. self.progressLayer.hidden = YES;
  122. if (stage == YYWebImageStageFinished) {
  123. self.maximumZoomScale = 3;
  124. if (image) {
  125. self->_itemDidLoad = YES;
  126. [self resizeSubviewSize];
  127. [self.imageView.layer addFadeAnimationWithDuration:0.1 curve:UIViewAnimationCurveLinear];
  128. }
  129. }
  130. }];
  131. [self resizeSubviewSize];
  132. }
  133. - (void)resizeSubviewSize {
  134. _imageContainerView.origin = CGPointZero;
  135. _imageContainerView.width = self.width;
  136. UIImage *image = _imageView.image;
  137. if (image.size.height / image.size.width > self.height / self.width) {
  138. _imageContainerView.height = floor(image.size.height / (image.size.width / self.width));
  139. } else {
  140. CGFloat height = image.size.height / image.size.width * self.width;
  141. if (height < 1 || isnan(height)) height = self.height;
  142. height = floor(height);
  143. _imageContainerView.height = height;
  144. _imageContainerView.centerY = self.height / 2;
  145. }
  146. if (_imageContainerView.height > self.height && _imageContainerView.height - self.height <= 1) {
  147. _imageContainerView.height = self.height;
  148. }
  149. self.contentSize = CGSizeMake(self.width, MAX(_imageContainerView.height, self.height));
  150. [self scrollRectToVisible:self.bounds animated:NO];
  151. if (_imageContainerView.height <= self.height) {
  152. self.alwaysBounceVertical = NO;
  153. } else {
  154. self.alwaysBounceVertical = YES;
  155. }
  156. [CATransaction begin];
  157. [CATransaction setDisableActions:YES];
  158. _imageView.frame = _imageContainerView.bounds;
  159. [CATransaction commit];
  160. }
  161. - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
  162. return _imageContainerView;
  163. }
  164. - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  165. UIView *subView = _imageContainerView;
  166. CGFloat offsetX = (scrollView.bounds.size.width > scrollView.contentSize.width)?
  167. (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0;
  168. CGFloat offsetY = (scrollView.bounds.size.height > scrollView.contentSize.height)?
  169. (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0;
  170. subView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX,
  171. scrollView.contentSize.height * 0.5 + offsetY);
  172. }
  173. @end
  174. @interface YYPhotoGroupView() <UIScrollViewDelegate, UIGestureRecognizerDelegate>
  175. @property (nonatomic, weak) UIView *fromView;
  176. @property (nonatomic, weak) UIView *toContainerView;
  177. @property (nonatomic, strong) UIImage *snapshotImage;
  178. @property (nonatomic, strong) UIImage *snapshorImageHideFromView;
  179. @property (nonatomic, strong) UIImageView *background;
  180. @property (nonatomic, strong) UIImageView *blurBackground;
  181. @property (nonatomic, strong) UIView *contentView;
  182. @property (nonatomic, strong) UIScrollView *scrollView;
  183. @property (nonatomic, strong) NSMutableArray *cells;
  184. @property (nonatomic, strong) UIPageControl *pager;
  185. @property (nonatomic, assign) CGFloat pagerCurrentPage;
  186. @property (nonatomic, assign) BOOL fromNavigationBarHidden;
  187. @property (nonatomic, assign) NSInteger fromItemIndex;
  188. @property (nonatomic, assign) BOOL isPresented;
  189. @property (nonatomic, strong) UIPanGestureRecognizer *panGesture;
  190. @property (nonatomic, assign) CGPoint panGestureBeginPoint;
  191. @end
  192. @implementation YYPhotoGroupView
  193. - (instancetype)initWithGroupItems:(NSArray *)groupItems {
  194. self = [super init];
  195. if (groupItems.count == 0) return nil;
  196. _groupItems = groupItems.copy;
  197. _blurEffectBackground = YES;
  198. NSString *model = [UIDevice currentDevice].machineModel;
  199. static NSMutableSet *oldDevices;
  200. static dispatch_once_t onceToken;
  201. dispatch_once(&onceToken, ^{
  202. oldDevices = [NSMutableSet new];
  203. [oldDevices addObject:@"iPod1,1"];
  204. [oldDevices addObject:@"iPod2,1"];
  205. [oldDevices addObject:@"iPod3,1"];
  206. [oldDevices addObject:@"iPod4,1"];
  207. [oldDevices addObject:@"iPod5,1"];
  208. [oldDevices addObject:@"iPhone1,1"];
  209. [oldDevices addObject:@"iPhone1,1"];
  210. [oldDevices addObject:@"iPhone1,2"];
  211. [oldDevices addObject:@"iPhone2,1"];
  212. [oldDevices addObject:@"iPhone3,1"];
  213. [oldDevices addObject:@"iPhone3,2"];
  214. [oldDevices addObject:@"iPhone3,3"];
  215. [oldDevices addObject:@"iPhone4,1"];
  216. [oldDevices addObject:@"iPad1,1"];
  217. [oldDevices addObject:@"iPad2,1"];
  218. [oldDevices addObject:@"iPad2,2"];
  219. [oldDevices addObject:@"iPad2,3"];
  220. [oldDevices addObject:@"iPad2,4"];
  221. [oldDevices addObject:@"iPad2,5"];
  222. [oldDevices addObject:@"iPad2,6"];
  223. [oldDevices addObject:@"iPad2,7"];
  224. [oldDevices addObject:@"iPad3,1"];
  225. [oldDevices addObject:@"iPad3,2"];
  226. [oldDevices addObject:@"iPad3,3"];
  227. });
  228. if ([oldDevices containsObject:model]) {
  229. _blurEffectBackground = NO;
  230. }
  231. self.backgroundColor = [UIColor clearColor];
  232. self.frame = [UIScreen mainScreen].bounds;
  233. self.clipsToBounds = YES;
  234. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismiss)];
  235. tap.delegate = self;
  236. [self addGestureRecognizer:tap];
  237. UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
  238. tap2.delegate = self;
  239. tap2.numberOfTapsRequired = 2;
  240. [tap requireGestureRecognizerToFail: tap2];
  241. [self addGestureRecognizer:tap2];
  242. UILongPressGestureRecognizer *press = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress)];
  243. press.delegate = self;
  244. [self addGestureRecognizer:press];
  245. if (kSystemVersion >= 7) {
  246. UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
  247. [self addGestureRecognizer:pan];
  248. _panGesture = pan;
  249. }
  250. _cells = @[].mutableCopy;
  251. _background = UIImageView.new;
  252. _background.frame = self.bounds;
  253. _background.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  254. _blurBackground = UIImageView.new;
  255. _blurBackground.frame = self.bounds;
  256. _blurBackground.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  257. _contentView = UIView.new;
  258. _contentView.frame = self.bounds;
  259. _contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  260. _scrollView = UIScrollView.new;
  261. _scrollView.frame = CGRectMake(-kPadding / 2, 0, self.width + kPadding, self.height);
  262. _scrollView.delegate = self;
  263. _scrollView.scrollsToTop = NO;
  264. _scrollView.pagingEnabled = YES;
  265. _scrollView.alwaysBounceHorizontal = groupItems.count > 1;
  266. _scrollView.showsHorizontalScrollIndicator = NO;
  267. _scrollView.showsVerticalScrollIndicator = NO;
  268. _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  269. _scrollView.delaysContentTouches = NO;
  270. _scrollView.canCancelContentTouches = YES;
  271. _pager = [[UIPageControl alloc] init];
  272. _pager.hidesForSinglePage = YES;
  273. _pager.userInteractionEnabled = NO;
  274. _pager.width = self.width - 36;
  275. _pager.height = 10;
  276. _pager.center = CGPointMake(self.width / 2, self.height - 18);
  277. _pager.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
  278. [self addSubview:_background];
  279. [self addSubview:_blurBackground];
  280. [self addSubview:_contentView];
  281. [_contentView addSubview:_scrollView];
  282. [_contentView addSubview:_pager];
  283. return self;
  284. }
  285. - (void)presentFromImageView:(UIView *)fromView
  286. toContainer:(UIView *)toContainer
  287. animated:(BOOL)animated
  288. completion:(void (^)(void))completion {
  289. if (!toContainer) return;
  290. _fromView = fromView;
  291. _toContainerView = toContainer;
  292. NSInteger page = -1;
  293. for (NSUInteger i = 0; i < self.groupItems.count; i++) {
  294. if (fromView == ((YYPhotoGroupItem *)self.groupItems[i]).thumbView) {
  295. page = (int)i;
  296. break;
  297. }
  298. }
  299. if (page == -1) page = 0;
  300. _fromItemIndex = page;
  301. _snapshotImage = [_toContainerView snapshotImageAfterScreenUpdates:NO];
  302. BOOL fromViewHidden = fromView.hidden;
  303. fromView.hidden = YES;
  304. _snapshorImageHideFromView = [_toContainerView snapshotImage];
  305. fromView.hidden = fromViewHidden;
  306. _background.image = _snapshorImageHideFromView;
  307. if (_blurEffectBackground) {
  308. _blurBackground.image = [_snapshorImageHideFromView imageByBlurDark]; //Same to UIBlurEffectStyleDark
  309. } else {
  310. _blurBackground.image = [UIImage imageWithColor:[UIColor blackColor]];
  311. }
  312. self.size = _toContainerView.size;
  313. self.blurBackground.alpha = 0;
  314. self.pager.alpha = 0;
  315. self.pager.numberOfPages = self.groupItems.count;
  316. self.pager.currentPage = page;
  317. [_toContainerView addSubview:self];
  318. _scrollView.contentSize = CGSizeMake(_scrollView.width * self.groupItems.count, _scrollView.height);
  319. [_scrollView scrollRectToVisible:CGRectMake(_scrollView.width * _pager.currentPage, 0, _scrollView.width, _scrollView.height) animated:NO];
  320. [self scrollViewDidScroll:_scrollView];
  321. [UIView setAnimationsEnabled:YES];
  322. _fromNavigationBarHidden = [UIApplication sharedApplication].statusBarHidden;
  323. [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:animated ? UIStatusBarAnimationFade : UIStatusBarAnimationNone];
  324. YYPhotoGroupCell *cell = [self cellForPage:self.currentPage];
  325. YYPhotoGroupItem *item = _groupItems[self.currentPage];
  326. if (!item.thumbClippedToTop) {
  327. NSString *imageKey = [[YYWebImageManager sharedManager] cacheKeyForURL:item.largeImageURL];
  328. if ([[YYWebImageManager sharedManager].cache getImageForKey:imageKey withType:YYImageCacheTypeMemory]) {
  329. cell.item = item;
  330. }
  331. }
  332. if (!cell.item) {
  333. cell.imageView.image = item.thumbImage;
  334. [cell resizeSubviewSize];
  335. }
  336. if (item.thumbClippedToTop) {
  337. CGRect fromFrame = [_fromView convertRect:_fromView.bounds toView:cell];
  338. CGRect originFrame = cell.imageContainerView.frame;
  339. CGFloat scale = fromFrame.size.width / cell.imageContainerView.width;
  340. cell.imageContainerView.centerX = CGRectGetMidX(fromFrame);
  341. cell.imageContainerView.height = fromFrame.size.height / scale;
  342. cell.imageContainerView.layer.transformScale = scale;
  343. cell.imageContainerView.centerY = CGRectGetMidY(fromFrame);
  344. float oneTime = animated ? 0.25 : 0;
  345. [UIView animateWithDuration:oneTime delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut animations:^{
  346. _blurBackground.alpha = 1;
  347. }completion:NULL];
  348. _scrollView.userInteractionEnabled = NO;
  349. [UIView animateWithDuration:oneTime delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
  350. cell.imageContainerView.layer.transformScale = 1;
  351. cell.imageContainerView.frame = originFrame;
  352. _pager.alpha = 1;
  353. }completion:^(BOOL finished) {
  354. _isPresented = YES;
  355. [self scrollViewDidScroll:_scrollView];
  356. _scrollView.userInteractionEnabled = YES;
  357. [self hidePager];
  358. if (completion) completion();
  359. }];
  360. } else {
  361. CGRect fromFrame = [_fromView convertRect:_fromView.bounds toView:cell.imageContainerView];
  362. cell.imageContainerView.clipsToBounds = NO;
  363. cell.imageView.frame = fromFrame;
  364. cell.imageView.contentMode = UIViewContentModeScaleAspectFill;
  365. float oneTime = animated ? 0.18 : 0;
  366. [UIView animateWithDuration:oneTime*2 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut animations:^{
  367. _blurBackground.alpha = 1;
  368. }completion:NULL];
  369. _scrollView.userInteractionEnabled = NO;
  370. [UIView animateWithDuration:oneTime delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut animations:^{
  371. cell.imageView.frame = cell.imageContainerView.bounds;
  372. cell.imageView.layer.transformScale = 1.01;
  373. }completion:^(BOOL finished) {
  374. [UIView animateWithDuration:oneTime delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseInOut animations:^{
  375. cell.imageView.layer.transformScale = 1.0;
  376. _pager.alpha = 1;
  377. }completion:^(BOOL finished) {
  378. cell.imageContainerView.clipsToBounds = YES;
  379. _isPresented = YES;
  380. [self scrollViewDidScroll:_scrollView];
  381. _scrollView.userInteractionEnabled = YES;
  382. [self hidePager];
  383. if (completion) completion();
  384. }];
  385. }];
  386. }
  387. }
  388. - (void)dismissAnimated:(BOOL)animated completion:(void (^)(void))completion {
  389. [UIView setAnimationsEnabled:YES];
  390. [[UIApplication sharedApplication] setStatusBarHidden:_fromNavigationBarHidden withAnimation:animated ? UIStatusBarAnimationFade : UIStatusBarAnimationNone];
  391. NSInteger currentPage = self.currentPage;
  392. YYPhotoGroupCell *cell = [self cellForPage:currentPage];
  393. YYPhotoGroupItem *item = _groupItems[currentPage];
  394. UIView *fromView = nil;
  395. if (_fromItemIndex == currentPage) {
  396. fromView = _fromView;
  397. } else {
  398. fromView = item.thumbView;
  399. }
  400. [self cancelAllImageLoad];
  401. _isPresented = NO;
  402. BOOL isFromImageClipped = fromView.layer.contentsRect.size.height < 1;
  403. [CATransaction begin];
  404. [CATransaction setDisableActions:YES];
  405. if (isFromImageClipped) {
  406. CGRect frame = cell.imageContainerView.frame;
  407. cell.imageContainerView.layer.anchorPoint = CGPointMake(0.5, 0);
  408. cell.imageContainerView.frame = frame;
  409. }
  410. cell.progressLayer.hidden = YES;
  411. [CATransaction commit];
  412. if (fromView == nil) {
  413. self.background.image = _snapshotImage;
  414. [UIView animateWithDuration:animated ? 0.25 : 0 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseOut animations:^{
  415. self.alpha = 0.0;
  416. self.scrollView.layer.transformScale = 0.95;
  417. self.scrollView.alpha = 0;
  418. self.pager.alpha = 0;
  419. self.blurBackground.alpha = 0;
  420. }completion:^(BOOL finished) {
  421. self.scrollView.layer.transformScale = 1;
  422. [self removeFromSuperview];
  423. [self cancelAllImageLoad];
  424. if (completion) completion();
  425. }];
  426. return;
  427. }
  428. if (_fromItemIndex != currentPage) {
  429. _background.image = _snapshotImage;
  430. [_background.layer addFadeAnimationWithDuration:0.25 curve:UIViewAnimationCurveEaseOut];
  431. } else {
  432. _background.image = _snapshorImageHideFromView;
  433. }
  434. if (isFromImageClipped) {
  435. [cell scrollToTopAnimated:NO];
  436. }
  437. [UIView animateWithDuration:animated ? 0.2 : 0 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionCurveEaseOut animations:^{
  438. _pager.alpha = 0.0;
  439. _blurBackground.alpha = 0.0;
  440. if (isFromImageClipped) {
  441. CGRect fromFrame = [fromView convertRect:fromView.bounds toView:cell];
  442. CGFloat scale = fromFrame.size.width / cell.imageContainerView.width * cell.zoomScale;
  443. CGFloat height = fromFrame.size.height / fromFrame.size.width * cell.imageContainerView.width;
  444. if (isnan(height)) height = cell.imageContainerView.height;
  445. cell.imageContainerView.height = height;
  446. cell.imageContainerView.center = CGPointMake(CGRectGetMidX(fromFrame), CGRectGetMinY(fromFrame));
  447. cell.imageContainerView.layer.transformScale = scale;
  448. } else {
  449. CGRect fromFrame = [fromView convertRect:fromView.bounds toView:cell.imageContainerView];
  450. cell.imageContainerView.clipsToBounds = NO;
  451. cell.imageView.contentMode = fromView.contentMode;
  452. cell.imageView.frame = fromFrame;
  453. }
  454. }completion:^(BOOL finished) {
  455. [UIView animateWithDuration:animated ? 0.15 : 0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
  456. self.alpha = 0;
  457. } completion:^(BOOL finished) {
  458. cell.imageContainerView.layer.anchorPoint = CGPointMake(0.5, 0.5);
  459. [self removeFromSuperview];
  460. if (completion) completion();
  461. }];
  462. }];
  463. }
  464. - (void)dismiss {
  465. [self dismissAnimated:YES completion:nil];
  466. }
  467. - (void)cancelAllImageLoad {
  468. [_cells enumerateObjectsUsingBlock:^(YYPhotoGroupCell *cell, NSUInteger idx, BOOL *stop) {
  469. [cell.imageView cancelCurrentImageRequest];
  470. }];
  471. }
  472. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  473. [self updateCellsForReuse];
  474. CGFloat floatPage = _scrollView.contentOffset.x / _scrollView.width;
  475. NSInteger page = _scrollView.contentOffset.x / _scrollView.width + 0.5;
  476. for (NSInteger i = page - 1; i <= page + 1; i++) { // preload left and right cell
  477. if (i >= 0 && i < self.groupItems.count) {
  478. YYPhotoGroupCell *cell = [self cellForPage:i];
  479. if (!cell) {
  480. YYPhotoGroupCell *cell = [self dequeueReusableCell];
  481. cell.page = i;
  482. cell.left = (self.width + kPadding) * i + kPadding / 2;
  483. if (_isPresented) {
  484. cell.item = self.groupItems[i];
  485. }
  486. [self.scrollView addSubview:cell];
  487. } else {
  488. if (_isPresented && !cell.item) {
  489. cell.item = self.groupItems[i];
  490. }
  491. }
  492. }
  493. }
  494. NSInteger intPage = floatPage + 0.5;
  495. intPage = intPage < 0 ? 0 : intPage >= _groupItems.count ? (int)_groupItems.count - 1 : intPage;
  496. _pager.currentPage = intPage;
  497. [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{
  498. _pager.alpha = 1;
  499. }completion:^(BOOL finish) {
  500. }];
  501. }
  502. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
  503. if (!decelerate) {
  504. [self hidePager];
  505. }
  506. }
  507. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
  508. [self hidePager];
  509. }
  510. - (void)hidePager {
  511. [UIView animateWithDuration:0.3 delay:0.8 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut animations:^{
  512. _pager.alpha = 0;
  513. }completion:^(BOOL finish) {
  514. }];
  515. }
  516. /// enqueue invisible cells for reuse
  517. - (void)updateCellsForReuse {
  518. for (YYPhotoGroupCell *cell in _cells) {
  519. if (cell.superview) {
  520. if (cell.left > _scrollView.contentOffset.x + _scrollView.width * 2||
  521. cell.right < _scrollView.contentOffset.x - _scrollView.width) {
  522. [cell removeFromSuperview];
  523. cell.page = -1;
  524. cell.item = nil;
  525. }
  526. }
  527. }
  528. }
  529. /// dequeue a reusable cell
  530. - (YYPhotoGroupCell *)dequeueReusableCell {
  531. YYPhotoGroupCell *cell = nil;
  532. for (cell in _cells) {
  533. if (!cell.superview) {
  534. return cell;
  535. }
  536. }
  537. cell = [YYPhotoGroupCell new];
  538. cell.frame = self.bounds;
  539. cell.imageContainerView.frame = self.bounds;
  540. cell.imageView.frame = cell.bounds;
  541. cell.page = -1;
  542. cell.item = nil;
  543. [_cells addObject:cell];
  544. return cell;
  545. }
  546. /// get the cell for specified page, nil if the cell is invisible
  547. - (YYPhotoGroupCell *)cellForPage:(NSInteger)page {
  548. for (YYPhotoGroupCell *cell in _cells) {
  549. if (cell.page == page) {
  550. return cell;
  551. }
  552. }
  553. return nil;
  554. }
  555. - (NSInteger)currentPage {
  556. NSInteger page = _scrollView.contentOffset.x / _scrollView.width + 0.5;
  557. if (page >= _groupItems.count) page = (NSInteger)_groupItems.count - 1;
  558. if (page < 0) page = 0;
  559. return page;
  560. }
  561. - (void)showHUD:(NSString *)msg {
  562. if (!msg.length) return;
  563. UIFont *font = [UIFont systemFontOfSize:17];
  564. CGSize size = [msg sizeForFont:font size:CGSizeMake(200, 200) mode:NSLineBreakByCharWrapping];
  565. UILabel *label = [UILabel new];
  566. label.size = CGSizePixelCeil(size);
  567. label.font = font;
  568. label.text = msg;
  569. label.textColor = [UIColor whiteColor];
  570. label.numberOfLines = 0;
  571. UIView *hud = [UIView new];
  572. hud.size = CGSizeMake(label.width + 20, label.height + 20);
  573. hud.backgroundColor = [UIColor colorWithWhite:0.000 alpha:0.650];
  574. hud.clipsToBounds = YES;
  575. hud.layer.cornerRadius = 8;
  576. label.center = CGPointMake(hud.width / 2, hud.height / 2);
  577. [hud addSubview:label];
  578. hud.center = CGPointMake(self.width / 2, self.height / 2);
  579. hud.alpha = 0;
  580. [self addSubview:hud];
  581. [UIView animateWithDuration:0.4 animations:^{
  582. hud.alpha = 1;
  583. }];
  584. double delayInSeconds = 1.5;
  585. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
  586. dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
  587. [UIView animateWithDuration:0.4 animations:^{
  588. hud.alpha = 0;
  589. } completion:^(BOOL finished) {
  590. [hud removeFromSuperview];
  591. }];
  592. });
  593. }
  594. - (void)doubleTap:(UITapGestureRecognizer *)g {
  595. if (!_isPresented) return;
  596. YYPhotoGroupCell *tile = [self cellForPage:self.currentPage];
  597. if (tile) {
  598. if (tile.zoomScale > 1) {
  599. [tile setZoomScale:1 animated:YES];
  600. } else {
  601. CGPoint touchPoint = [g locationInView:tile.imageView];
  602. CGFloat newZoomScale = tile.maximumZoomScale;
  603. CGFloat xsize = self.width / newZoomScale;
  604. CGFloat ysize = self.height / newZoomScale;
  605. [tile zoomToRect:CGRectMake(touchPoint.x - xsize/2, touchPoint.y - ysize/2, xsize, ysize) animated:YES];
  606. }
  607. }
  608. }
  609. - (void)longPress {
  610. if (!_isPresented) return;
  611. YYPhotoGroupCell *tile = [self cellForPage:self.currentPage];
  612. if (!tile.imageView.image) return;
  613. // try to save original image data if the image contains multi-frame (such as GIF/APNG)
  614. id imageItem = [tile.imageView.image imageDataRepresentation];
  615. YYImageType type = YYImageDetectType((__bridge CFDataRef)(imageItem));
  616. if (type != YYImageTypePNG &&
  617. type != YYImageTypeJPEG &&
  618. type != YYImageTypeGIF) {
  619. imageItem = tile.imageView.image;
  620. }
  621. UIActivityViewController *activityViewController =
  622. [[UIActivityViewController alloc] initWithActivityItems:@[imageItem] applicationActivities:nil];
  623. if ([activityViewController respondsToSelector:@selector(popoverPresentationController)]) {
  624. activityViewController.popoverPresentationController.sourceView = self;
  625. }
  626. UIViewController *toVC = self.toContainerView.viewController;
  627. if (!toVC) toVC = self.viewController;
  628. [toVC presentViewController:activityViewController animated:YES completion:nil];
  629. }
  630. - (void)pan:(UIPanGestureRecognizer *)g {
  631. switch (g.state) {
  632. case UIGestureRecognizerStateBegan: {
  633. if (_isPresented) {
  634. _panGestureBeginPoint = [g locationInView:self];
  635. } else {
  636. _panGestureBeginPoint = CGPointZero;
  637. }
  638. } break;
  639. case UIGestureRecognizerStateChanged: {
  640. if (_panGestureBeginPoint.x == 0 && _panGestureBeginPoint.y == 0) return;
  641. CGPoint p = [g locationInView:self];
  642. CGFloat deltaY = p.y - _panGestureBeginPoint.y;
  643. _scrollView.top = deltaY;
  644. CGFloat alphaDelta = 160;
  645. CGFloat alpha = (alphaDelta - fabs(deltaY) + 50) / alphaDelta;
  646. alpha = YY_CLAMP(alpha, 0, 1);
  647. [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear animations:^{
  648. _blurBackground.alpha = alpha;
  649. _pager.alpha = alpha;
  650. } completion:nil];
  651. } break;
  652. case UIGestureRecognizerStateEnded: {
  653. if (_panGestureBeginPoint.x == 0 && _panGestureBeginPoint.y == 0) return;
  654. CGPoint v = [g velocityInView:self];
  655. CGPoint p = [g locationInView:self];
  656. CGFloat deltaY = p.y - _panGestureBeginPoint.y;
  657. if (fabs(v.y) > 1000 || fabs(deltaY) > 120) {
  658. [self cancelAllImageLoad];
  659. _isPresented = NO;
  660. [[UIApplication sharedApplication] setStatusBarHidden:_fromNavigationBarHidden withAnimation:UIStatusBarAnimationFade];
  661. BOOL moveToTop = (v.y < - 50 || (v.y < 50 && deltaY < 0));
  662. CGFloat vy = fabs(v.y);
  663. if (vy < 1) vy = 1;
  664. CGFloat duration = (moveToTop ? _scrollView.bottom : self.height - _scrollView.top) / vy;
  665. duration *= 0.8;
  666. duration = YY_CLAMP(duration, 0.05, 0.3);
  667. [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionBeginFromCurrentState animations:^{
  668. _blurBackground.alpha = 0;
  669. _pager.alpha = 0;
  670. if (moveToTop) {
  671. _scrollView.bottom = 0;
  672. } else {
  673. _scrollView.top = self.height;
  674. }
  675. } completion:^(BOOL finished) {
  676. [self removeFromSuperview];
  677. }];
  678. _background.image = _snapshotImage;
  679. [_background.layer addFadeAnimationWithDuration:0.3 curve:UIViewAnimationCurveEaseInOut];
  680. } else {
  681. [UIView animateWithDuration:0.4 delay:0 usingSpringWithDamping:0.9 initialSpringVelocity:v.y / 1000 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState animations:^{
  682. _scrollView.top = 0;
  683. _blurBackground.alpha = 1;
  684. _pager.alpha = 1;
  685. } completion:^(BOOL finished) {
  686. }];
  687. }
  688. } break;
  689. case UIGestureRecognizerStateCancelled : {
  690. _scrollView.top = 0;
  691. _blurBackground.alpha = 1;
  692. }
  693. default:break;
  694. }
  695. }
  696. @end