YYPhotoGroupView.m 31 KB

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