TZPhotoPreviewCell.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. //
  2. // TZPhotoPreviewCell.m
  3. // TZImagePickerController
  4. //
  5. // Created by 谭真 on 15/12/24.
  6. // Copyright © 2015年 谭真. All rights reserved.
  7. //
  8. #import "TZPhotoPreviewCell.h"
  9. #import "TZAssetModel.h"
  10. #import "UIView+Layout.h"
  11. #import "TZImageManager.h"
  12. #import "TZProgressView.h"
  13. #import "TZImageCropManager.h"
  14. #import <MediaPlayer/MediaPlayer.h>
  15. #import "TZImagePickerController.h"
  16. @implementation TZAssetPreviewCell
  17. - (instancetype)initWithFrame:(CGRect)frame {
  18. self = [super initWithFrame:frame];
  19. if (self) {
  20. self.backgroundColor = [UIColor blackColor];
  21. [self configSubviews];
  22. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(photoPreviewCollectionViewDidScroll) name:@"photoPreviewCollectionViewDidScroll" object:nil];
  23. }
  24. return self;
  25. }
  26. - (void)configSubviews {
  27. }
  28. #pragma mark - Notification
  29. - (void)photoPreviewCollectionViewDidScroll {
  30. }
  31. - (void)dealloc {
  32. [[NSNotificationCenter defaultCenter] removeObserver:self];
  33. }
  34. @end
  35. @implementation TZPhotoPreviewCell
  36. - (void)configSubviews {
  37. self.previewView = [[TZPhotoPreviewView alloc] initWithFrame:CGRectZero];
  38. __weak typeof(self) weakSelf = self;
  39. [self.previewView setSingleTapGestureBlock:^{
  40. __strong typeof(weakSelf) strongSelf = weakSelf;
  41. if (strongSelf.singleTapGestureBlock) {
  42. strongSelf.singleTapGestureBlock();
  43. }
  44. }];
  45. [self.previewView setImageProgressUpdateBlock:^(double progress) {
  46. __strong typeof(weakSelf) strongSelf = weakSelf;
  47. if (strongSelf.imageProgressUpdateBlock) {
  48. strongSelf.imageProgressUpdateBlock(progress);
  49. }
  50. }];
  51. [self addSubview:self.previewView];
  52. }
  53. - (void)setModel:(TZAssetModel *)model {
  54. [super setModel:model];
  55. _previewView.asset = model.asset;
  56. }
  57. - (void)recoverSubviews {
  58. [_previewView recoverSubviews];
  59. }
  60. - (void)setAllowCrop:(BOOL)allowCrop {
  61. _allowCrop = allowCrop;
  62. _previewView.allowCrop = allowCrop;
  63. }
  64. - (void)setScaleAspectFillCrop:(BOOL)scaleAspectFillCrop {
  65. _scaleAspectFillCrop = scaleAspectFillCrop;
  66. _previewView.scaleAspectFillCrop = scaleAspectFillCrop;
  67. }
  68. - (void)setCropRect:(CGRect)cropRect {
  69. _cropRect = cropRect;
  70. _previewView.cropRect = cropRect;
  71. }
  72. - (void)layoutSubviews {
  73. [super layoutSubviews];
  74. self.previewView.frame = self.bounds;
  75. }
  76. @end
  77. @interface TZPhotoPreviewView ()<UIScrollViewDelegate>
  78. @property (assign, nonatomic) BOOL isRequestingGIF;
  79. @end
  80. @implementation TZPhotoPreviewView
  81. - (instancetype)initWithFrame:(CGRect)frame {
  82. self = [super initWithFrame:frame];
  83. if (self) {
  84. _scrollView = [[UIScrollView alloc] init];
  85. _scrollView.bouncesZoom = YES;
  86. _scrollView.maximumZoomScale = 2.5;
  87. _scrollView.minimumZoomScale = 1.0;
  88. _scrollView.multipleTouchEnabled = YES;
  89. _scrollView.delegate = self;
  90. _scrollView.scrollsToTop = NO;
  91. _scrollView.showsHorizontalScrollIndicator = NO;
  92. _scrollView.showsVerticalScrollIndicator = YES;
  93. _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  94. _scrollView.delaysContentTouches = NO;
  95. _scrollView.canCancelContentTouches = YES;
  96. _scrollView.alwaysBounceVertical = NO;
  97. if (@available(iOS 11, *)) {
  98. _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
  99. }
  100. [self addSubview:_scrollView];
  101. _imageContainerView = [[UIView alloc] init];
  102. _imageContainerView.clipsToBounds = YES;
  103. _imageContainerView.contentMode = UIViewContentModeScaleAspectFill;
  104. [_scrollView addSubview:_imageContainerView];
  105. _imageView = [[UIImageView alloc] init];
  106. _imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500];
  107. _imageView.contentMode = UIViewContentModeScaleAspectFill;
  108. _imageView.clipsToBounds = YES;
  109. [_imageContainerView addSubview:_imageView];
  110. UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
  111. [self addGestureRecognizer:tap1];
  112. UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
  113. tap2.numberOfTapsRequired = 2;
  114. [tap1 requireGestureRecognizerToFail:tap2];
  115. [self addGestureRecognizer:tap2];
  116. [self configProgressView];
  117. }
  118. return self;
  119. }
  120. - (void)configProgressView {
  121. _progressView = [[TZProgressView alloc] init];
  122. _progressView.hidden = YES;
  123. [self addSubview:_progressView];
  124. }
  125. - (void)setModel:(TZAssetModel *)model {
  126. _model = model;
  127. self.isRequestingGIF = NO;
  128. [_scrollView setZoomScale:1.0 animated:NO];
  129. if (model.type == TZAssetModelMediaTypePhotoGif) {
  130. // 先显示缩略图
  131. [[TZImageManager manager] getPhotoWithAsset:model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
  132. self.imageView.image = photo;
  133. [self resizeSubviews];
  134. if (self.isRequestingGIF) {
  135. return;
  136. }
  137. // 再显示gif动图
  138. self.isRequestingGIF = YES;
  139. [[TZImageManager manager] getOriginalPhotoDataWithAsset:model.asset progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
  140. progress = progress > 0.02 ? progress : 0.02;
  141. dispatch_async(dispatch_get_main_queue(), ^{
  142. self.progressView.progress = progress;
  143. if (progress >= 1) {
  144. self.progressView.hidden = YES;
  145. } else {
  146. self.progressView.hidden = NO;
  147. }
  148. });
  149. #ifdef DEBUG
  150. NSLog(@"[TZImagePickerController] getOriginalPhotoDataWithAsset:%f error:%@", progress, error);
  151. #endif
  152. } completion:^(NSData *data, NSDictionary *info, BOOL isDegraded) {
  153. if (!isDegraded) {
  154. self.isRequestingGIF = NO;
  155. self.progressView.hidden = YES;
  156. if ([TZImagePickerConfig sharedInstance].gifImagePlayBlock) {
  157. [TZImagePickerConfig sharedInstance].gifImagePlayBlock(self, self.imageView, data, info);
  158. } else {
  159. self.imageView.image = [UIImage sd_tz_animatedGIFWithData:data];
  160. }
  161. [self resizeSubviews];
  162. }
  163. }];
  164. } progressHandler:nil networkAccessAllowed:NO];
  165. } else {
  166. self.asset = model.asset;
  167. }
  168. }
  169. - (void)setAsset:(PHAsset *)asset {
  170. if (_asset && self.imageRequestID) {
  171. [[PHImageManager defaultManager] cancelImageRequest:self.imageRequestID];
  172. }
  173. _asset = asset;
  174. self.imageRequestID = [[TZImageManager manager] getPhotoWithAsset:asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
  175. if (![asset isEqual:self->_asset]) return;
  176. self.imageView.image = photo;
  177. [self resizeSubviews];
  178. if (self.imageView.tz_height && self.allowCrop) {
  179. CGFloat scale = MAX(self.cropRect.size.width / self.imageView.tz_width, self.cropRect.size.height / self.imageView.tz_height);
  180. if (self.scaleAspectFillCrop && scale > 1) { // 如果设置图片缩放裁剪并且图片需要缩放
  181. CGFloat multiple = self.scrollView.maximumZoomScale / self.scrollView.minimumZoomScale;
  182. self.scrollView.minimumZoomScale = scale;
  183. self.scrollView.maximumZoomScale = scale * MAX(multiple, 2);
  184. [self.scrollView setZoomScale:scale animated:YES];
  185. }
  186. }
  187. self->_progressView.hidden = YES;
  188. if (self.imageProgressUpdateBlock) {
  189. self.imageProgressUpdateBlock(1);
  190. }
  191. if (!isDegraded) {
  192. self.imageRequestID = 0;
  193. }
  194. } progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
  195. if (![asset isEqual:self->_asset]) return;
  196. self->_progressView.hidden = NO;
  197. [self bringSubviewToFront:self->_progressView];
  198. progress = progress > 0.02 ? progress : 0.02;
  199. self->_progressView.progress = progress;
  200. if (self.imageProgressUpdateBlock && progress < 1) {
  201. self.imageProgressUpdateBlock(progress);
  202. }
  203. if (progress >= 1) {
  204. self->_progressView.hidden = YES;
  205. self.imageRequestID = 0;
  206. }
  207. } networkAccessAllowed:YES];
  208. [self configMaximumZoomScale];
  209. }
  210. - (void)recoverSubviews {
  211. [_scrollView setZoomScale:_scrollView.minimumZoomScale animated:NO];
  212. [self resizeSubviews];
  213. }
  214. - (void)resizeSubviews {
  215. _imageContainerView.tz_origin = CGPointZero;
  216. _imageContainerView.tz_width = self.scrollView.tz_width;
  217. UIImage *image = _imageView.image;
  218. if (image.size.height / image.size.width > self.tz_height / self.scrollView.tz_width) {
  219. _imageContainerView.tz_height = floor(image.size.height / (image.size.width / self.scrollView.tz_width));
  220. } else {
  221. CGFloat height = image.size.height / image.size.width * self.scrollView.tz_width;
  222. if (height < 1 || isnan(height)) height = self.tz_height;
  223. height = floor(height);
  224. _imageContainerView.tz_height = height;
  225. _imageContainerView.tz_centerY = self.tz_height / 2;
  226. }
  227. if (_imageContainerView.tz_height > self.tz_height && _imageContainerView.tz_height - self.tz_height <= 1) {
  228. _imageContainerView.tz_height = self.tz_height;
  229. }
  230. CGFloat contentSizeH = MAX(_imageContainerView.tz_height, self.tz_height);
  231. _scrollView.contentSize = CGSizeMake(self.scrollView.tz_width, contentSizeH);
  232. [_scrollView scrollRectToVisible:self.bounds animated:NO];
  233. _scrollView.alwaysBounceVertical = _imageContainerView.tz_height <= self.tz_height ? NO : YES;
  234. _imageView.frame = _imageContainerView.bounds;
  235. [self refreshScrollViewContentSize];
  236. }
  237. - (void)configMaximumZoomScale {
  238. _scrollView.maximumZoomScale = _allowCrop ? 4.0 : 2.5;
  239. if ([self.asset isKindOfClass:[PHAsset class]]) {
  240. PHAsset *phAsset = (PHAsset *)self.asset;
  241. CGFloat aspectRatio = phAsset.pixelWidth / (CGFloat)phAsset.pixelHeight;
  242. // 优化超宽图片的显示
  243. if (aspectRatio > 1.5) {
  244. self.scrollView.maximumZoomScale *= aspectRatio / 1.5;
  245. }
  246. }
  247. }
  248. - (void)refreshScrollViewContentSize {
  249. if (_allowCrop) {
  250. // 1.7.2 如果允许裁剪,需要让图片的任意部分都能在裁剪框内,于是对_scrollView做了如下处理:
  251. // 1.让contentSize增大(裁剪框右下角的图片部分)
  252. CGFloat contentWidthAdd = self.scrollView.tz_width - CGRectGetMaxX(_cropRect);
  253. CGFloat contentHeightAdd = (MIN(_imageContainerView.tz_height, self.tz_height) - self.cropRect.size.height) / 2;
  254. CGFloat newSizeW = self.scrollView.contentSize.width + contentWidthAdd;
  255. CGFloat newSizeH = MAX(self.scrollView.contentSize.height, self.tz_height) + contentHeightAdd;
  256. _scrollView.contentSize = CGSizeMake(newSizeW, newSizeH);
  257. _scrollView.alwaysBounceVertical = YES;
  258. // 2.让scrollView新增滑动区域(裁剪框左上角的图片部分)
  259. if (contentHeightAdd > 0 || contentWidthAdd > 0) {
  260. _scrollView.contentInset = UIEdgeInsetsMake(contentHeightAdd, _cropRect.origin.x, 0, 0);
  261. } else {
  262. _scrollView.contentInset = UIEdgeInsetsZero;
  263. }
  264. }
  265. }
  266. - (void)layoutSubviews {
  267. [super layoutSubviews];
  268. _scrollView.frame = CGRectMake(10, 0, self.tz_width - 20, self.tz_height);
  269. static CGFloat progressWH = 40;
  270. CGFloat progressX = (self.tz_width - progressWH) / 2;
  271. CGFloat progressY = (self.tz_height - progressWH) / 2;
  272. _progressView.frame = CGRectMake(progressX, progressY, progressWH, progressWH);
  273. [self recoverSubviews];
  274. }
  275. #pragma mark - UITapGestureRecognizer Event
  276. - (void)doubleTap:(UITapGestureRecognizer *)tap {
  277. if (_scrollView.zoomScale > _scrollView.minimumZoomScale) {
  278. _scrollView.contentInset = UIEdgeInsetsZero;
  279. [_scrollView setZoomScale:_scrollView.minimumZoomScale animated:YES];
  280. } else {
  281. CGPoint touchPoint = [tap locationInView:self.imageView];
  282. CGFloat newZoomScale = _scrollView.maximumZoomScale;
  283. CGFloat xsize = self.frame.size.width / newZoomScale;
  284. CGFloat ysize = self.frame.size.height / newZoomScale;
  285. [_scrollView zoomToRect:CGRectMake(touchPoint.x - xsize/2, touchPoint.y - ysize/2, xsize, ysize) animated:YES];
  286. }
  287. }
  288. - (void)singleTap:(UITapGestureRecognizer *)tap {
  289. if (self.singleTapGestureBlock) {
  290. self.singleTapGestureBlock();
  291. }
  292. }
  293. #pragma mark - UIScrollViewDelegate
  294. - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
  295. return _imageContainerView;
  296. }
  297. - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
  298. scrollView.contentInset = UIEdgeInsetsZero;
  299. }
  300. - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  301. [self refreshImageContainerViewCenter];
  302. }
  303. - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
  304. [self refreshScrollViewContentSize];
  305. }
  306. #pragma mark - Private
  307. - (void)refreshImageContainerViewCenter {
  308. CGFloat offsetX = (_scrollView.tz_width > _scrollView.contentSize.width) ? ((_scrollView.tz_width - _scrollView.contentSize.width) * 0.5) : 0.0;
  309. CGFloat offsetY = (_scrollView.tz_height > _scrollView.contentSize.height) ? ((_scrollView.tz_height - _scrollView.contentSize.height) * 0.5) : 0.0;
  310. self.imageContainerView.center = CGPointMake(_scrollView.contentSize.width * 0.5 + offsetX, _scrollView.contentSize.height * 0.5 + offsetY);
  311. }
  312. @end
  313. @implementation TZVideoPreviewCell
  314. - (void)configSubviews {
  315. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil];
  316. }
  317. - (void)configPlayButton {
  318. if (_playButton) {
  319. [_playButton removeFromSuperview];
  320. }
  321. _playButton = [UIButton buttonWithType:UIButtonTypeCustom];
  322. [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
  323. [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted];
  324. [_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside];
  325. [self addSubview:_playButton];
  326. }
  327. - (void)setModel:(TZAssetModel *)model {
  328. [super setModel:model];
  329. [self configMoviePlayer];
  330. }
  331. - (void)setVideoURL:(NSURL *)videoURL {
  332. _videoURL = videoURL;
  333. [self configMoviePlayer];
  334. }
  335. - (void)configMoviePlayer {
  336. if (_player) {
  337. [_playerLayer removeFromSuperlayer];
  338. _playerLayer = nil;
  339. [_player pause];
  340. _player = nil;
  341. }
  342. if (self.model && self.model.asset) {
  343. [[TZImageManager manager] getPhotoWithAsset:self.model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
  344. self.cover = photo;
  345. }];
  346. [[TZImageManager manager] getVideoWithAsset:self.model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) {
  347. dispatch_async(dispatch_get_main_queue(), ^{
  348. [self configPlayerWithItem:playerItem];
  349. });
  350. }];
  351. } else {
  352. AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:self.videoURL];
  353. [self configPlayerWithItem:playerItem];
  354. }
  355. }
  356. - (void)configPlayerWithItem:(AVPlayerItem *)playerItem {
  357. self.player = [AVPlayer playerWithPlayerItem:playerItem];
  358. self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
  359. self.playerLayer.backgroundColor = [UIColor blackColor].CGColor;
  360. self.playerLayer.frame = self.bounds;
  361. [self.layer addSublayer:self.playerLayer];
  362. [self configPlayButton];
  363. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
  364. }
  365. - (void)layoutSubviews {
  366. [super layoutSubviews];
  367. _playerLayer.frame = self.bounds;
  368. _playButton.frame = CGRectMake(0, 64, self.tz_width, self.tz_height - 64 - 44);
  369. }
  370. - (void)photoPreviewCollectionViewDidScroll {
  371. if (_player && _player.rate != 0.0) {
  372. [self pausePlayerAndShowNaviBar];
  373. }
  374. }
  375. #pragma mark - Notification
  376. - (void)appWillResignActiveNotification {
  377. if (_player && _player.rate != 0.0) {
  378. [self pausePlayerAndShowNaviBar];
  379. }
  380. }
  381. #pragma mark - Click Event
  382. - (void)playButtonClick {
  383. CMTime currentTime = _player.currentItem.currentTime;
  384. CMTime durationTime = _player.currentItem.duration;
  385. if (_player.rate == 0.0f) {
  386. if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)];
  387. [_player play];
  388. [_playButton setImage:nil forState:UIControlStateNormal];
  389. [UIApplication sharedApplication].statusBarHidden = YES;
  390. if (self.singleTapGestureBlock) {
  391. self.singleTapGestureBlock();
  392. }
  393. } else {
  394. [self pausePlayerAndShowNaviBar];
  395. }
  396. }
  397. - (void)pausePlayerAndShowNaviBar {
  398. [_player pause];
  399. [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
  400. if (self.singleTapGestureBlock) {
  401. self.singleTapGestureBlock();
  402. }
  403. }
  404. @end
  405. @implementation TZGifPreviewCell
  406. - (void)configSubviews {
  407. [self configPreviewView];
  408. }
  409. - (void)configPreviewView {
  410. _previewView = [[TZPhotoPreviewView alloc] initWithFrame:CGRectZero];
  411. __weak typeof(self) weakSelf = self;
  412. [_previewView setSingleTapGestureBlock:^{
  413. __strong typeof(weakSelf) strongSelf = weakSelf;
  414. [strongSelf signleTapAction];
  415. }];
  416. [self addSubview:_previewView];
  417. }
  418. - (void)setModel:(TZAssetModel *)model {
  419. [super setModel:model];
  420. _previewView.model = self.model;
  421. }
  422. - (void)layoutSubviews {
  423. [super layoutSubviews];
  424. _previewView.frame = self.bounds;
  425. }
  426. #pragma mark - Click Event
  427. - (void)signleTapAction {
  428. if (self.singleTapGestureBlock) {
  429. self.singleTapGestureBlock();
  430. }
  431. }
  432. @end