FDPhotoGroupView.m 31 KB

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