diff --git a/TZImagePickerController.xcodeproj/project.pbxproj b/TZImagePickerController.xcodeproj/project.pbxproj index 8818a96a..c362ff6b 100644 --- a/TZImagePickerController.xcodeproj/project.pbxproj +++ b/TZImagePickerController.xcodeproj/project.pbxproj @@ -531,7 +531,7 @@ TargetAttributes = { 900E657B1C2BB8D5003D9A9E = { CreatedOnToolsVersion = 7.2; - DevelopmentTeam = 3TA49P2Q58; + DevelopmentTeam = 9YHXDAVF4M; ProvisioningStyle = Automatic; }; 900E65941C2BB8D5003D9A9E = { @@ -854,7 +854,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 3TA49P2Q58; + DEVELOPMENT_TEAM = 9YHXDAVF4M; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/TZImagePickerController", @@ -879,7 +879,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = LTFQDC2QVX; + DEVELOPMENT_TEAM = 9YHXDAVF4M; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/TZImagePickerController", diff --git a/TZImagePickerController/TZImagePickerController/TZAssetCell.h b/TZImagePickerController/TZImagePickerController/TZAssetCell.h index 67051e8f..4a194c0c 100644 --- a/TZImagePickerController/TZImagePickerController/TZAssetCell.h +++ b/TZImagePickerController/TZImagePickerController/TZAssetCell.h @@ -26,12 +26,14 @@ typedef enum : NSUInteger { @property (nonatomic, copy) void (^didSelectPhotoBlock)(BOOL); @property (nonatomic, assign) TZAssetCellType type; @property (nonatomic, assign) BOOL allowPickingGif; +@property (nonatomic, assign) BOOL allowPickingLiveImage; @property (nonatomic, assign) BOOL allowPickingMultipleVideo; @property (nonatomic, copy) NSString *representedAssetIdentifier; @property (nonatomic, assign) int32_t imageRequestID; @property (nonatomic, strong) UIImage *photoSelImage; @property (nonatomic, strong) UIImage *photoDefImage; +@property (nonatomic, strong) UIImageView *toggleLiveImageView; @property (nonatomic, assign) BOOL showSelectBtn; @property (assign, nonatomic) BOOL allowPreview; diff --git a/TZImagePickerController/TZImagePickerController/TZAssetCell.m b/TZImagePickerController/TZImagePickerController/TZAssetCell.m index 8cae3bd3..3268dd68 100644 --- a/TZImagePickerController/TZImagePickerController/TZAssetCell.m +++ b/TZImagePickerController/TZImagePickerController/TZAssetCell.m @@ -109,6 +109,8 @@ - (void)setType:(TZAssetCellType)type { _selectPhotoButton.hidden = YES; } + _toggleLiveImageView.hidden = YES; + if (type == TZAssetCellTypeVideo) { self.bottomView.hidden = NO; self.timeLength.text = _model.timeLength; @@ -121,6 +123,8 @@ - (void)setType:(TZAssetCellType)type { self.videoImgView.hidden = YES; _timeLength.tz_left = 5; _timeLength.textAlignment = NSTextAlignmentLeft; + }else if (type == TZAssetCellTypeLivePhoto && self.allowPickingLiveImage) { + self.toggleLiveImageView.hidden = NO; } } @@ -338,7 +342,18 @@ - (UILabel *)indexLabel { } return _indexLabel; } - +- (UIImageView *)toggleLiveImageView{ + if (!_toggleLiveImageView) { + UIImage *image = [UIImage tz_imageNamedFromMyBundle:@"photo_livephoto"]; + if (@available(iOS 13.0, *)) { + image = [[UIImage systemImageNamed:@"livephoto"] imageWithTintColor:UIColor.whiteColor renderingMode:UIImageRenderingModeAlwaysOriginal]; + } + _toggleLiveImageView = [[UIImageView alloc]initWithImage:image]; + _toggleLiveImageView.contentMode = UIViewContentModeScaleAspectFit; + [self.contentView addSubview:_toggleLiveImageView]; + } + return _toggleLiveImageView; +} - (TZProgressView *)progressView { if (_progressView == nil) { _progressView = [[TZProgressView alloc] init]; @@ -365,6 +380,8 @@ - (void)layoutSubviews { _indexLabel.frame = _selectImageView.frame; _imageView.frame = self.bounds; + self.toggleLiveImageView.frame = CGRectMake(3, 3, 18, 18); + static CGFloat progressWH = 20; CGFloat progressXY = (self.tz_width - progressWH) / 2; _progressView.frame = CGRectMake(progressXY, progressXY, progressWH, progressWH); @@ -381,7 +398,8 @@ - (void)layoutSubviews { [self.contentView bringSubviewToFront:_selectPhotoButton]; [self.contentView bringSubviewToFront:_selectImageView]; [self.contentView bringSubviewToFront:_indexLabel]; - + [self.contentView bringSubviewToFront:_toggleLiveImageView]; + if (self.assetCellDidLayoutSubviewsBlock) { self.assetCellDidLayoutSubviewsBlock(self, _imageView, _selectImageView, _indexLabel, _bottomView, _timeLength, _videoImgView); } diff --git a/TZImagePickerController/TZImagePickerController/TZAssetModel.h b/TZImagePickerController/TZImagePickerController/TZAssetModel.h index b8bb80ff..475ee7ab 100755 --- a/TZImagePickerController/TZImagePickerController/TZAssetModel.h +++ b/TZImagePickerController/TZImagePickerController/TZAssetModel.h @@ -26,6 +26,8 @@ typedef enum : NSUInteger { @property (nonatomic, assign) TZAssetModelMediaType type; @property (nonatomic, copy) NSString *timeLength; @property (nonatomic, assign) BOOL iCloudFailed; +/// 是否使用 Live Photo 模式,默认 YES +@property (nonatomic, assign) BOOL useLivePhoto; /// Init a photo dataModel With a PHAsset /// 用一个PHAsset实例,初始化一个照片模型 diff --git a/TZImagePickerController/TZImagePickerController/TZAssetModel.m b/TZImagePickerController/TZImagePickerController/TZAssetModel.m index 4a534fdc..78a241ce 100644 --- a/TZImagePickerController/TZImagePickerController/TZAssetModel.m +++ b/TZImagePickerController/TZImagePickerController/TZAssetModel.m @@ -16,6 +16,7 @@ + (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type model.asset = asset; model.isSelected = NO; model.type = type; + model.useLivePhoto = (model.type == TZAssetModelMediaTypeLivePhoto ? YES:NO); return model; } diff --git a/TZImagePickerController/TZImagePickerController/TZImageManager.h b/TZImagePickerController/TZImagePickerController/TZImageManager.h index f7f5ff15..dae312a8 100755 --- a/TZImagePickerController/TZImagePickerController/TZImageManager.h +++ b/TZImagePickerController/TZImagePickerController/TZImageManager.h @@ -80,6 +80,9 @@ // 该方法中,completion只会走一次 - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion; - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion; +/// Get livePhoto 获得实况图照片 +- (PHImageRequestID)getLivePhotoWithAsset:(PHAsset *)asset completion:(void (^)(PHLivePhoto *livePhoto, NSDictionary *info))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler; + /// Get Image For VideoURL - (UIImage *)getImageWithVideoURL:(NSURL *)videoURL; diff --git a/TZImagePickerController/TZImagePickerController/TZImageManager.m b/TZImagePickerController/TZImagePickerController/TZImageManager.m index 3e876033..6b3d5431 100755 --- a/TZImagePickerController/TZImagePickerController/TZImageManager.m +++ b/TZImagePickerController/TZImagePickerController/TZImageManager.m @@ -17,6 +17,9 @@ @interface TZImageManager () @end @implementation TZImageManager +{ + PHCachingImageManager *_phCachingImageManager; +} CGSize AssetGridThumbnailSize; CGFloat TZScreenWidth; @@ -257,7 +260,8 @@ - (TZAssetModel *)assetModelWithAsset:(PHAsset *)asset allowPickingVideo:(BOOL)a if (!allowPickingVideo && type == TZAssetModelMediaTypeVideo) return nil; if (!allowPickingImage && type == TZAssetModelMediaTypePhoto) return nil; if (!allowPickingImage && type == TZAssetModelMediaTypePhotoGif) return nil; - + if (!allowPickingImage && type == TZAssetModelMediaTypeLivePhoto) return nil; + PHAsset *phAsset = (PHAsset *)asset; if (self.hideWhenCanNotSelect) { // 过滤掉尺寸不满足要求的图片 @@ -278,7 +282,10 @@ - (TZAssetModelMediaType)getAssetType:(PHAsset *)asset { else if (phAsset.mediaType == PHAssetMediaTypeAudio) type = TZAssetModelMediaTypeAudio; else if (phAsset.mediaType == PHAssetMediaTypeImage) { if (@available(iOS 9.1, *)) { - // if (asset.mediaSubtypes == PHAssetMediaSubtypePhotoLive) type = TZAssetModelMediaTypeLivePhoto; + // PHAssetMediaSubtype 是一个 位掩码(bitmask)类型,多个值可以 按位“或”组合在一起,判断一个类型是否包含某一项(比如是否包含 Live)时,不能用 == + // asset.mediaSubtypes & PHAssetMediaSubtypePhotoLive 等价于: + // 判断 asset.mediaSubtypes 的二进制值中,是否包含 Live Photo(1 << 2,对应二进制位是00000100) + if (asset.mediaSubtypes & PHAssetMediaSubtypePhotoLive) type = TZAssetModelMediaTypeLivePhoto; } // Gif if ([[phAsset valueForKey:@"filename"] hasSuffix:@"GIF"]) { @@ -504,6 +511,31 @@ - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset progressHandl }]; } +- (PHImageRequestID)getLivePhotoWithAsset:(PHAsset *)asset completion:(void (^)(PHLivePhoto *livePhoto, NSDictionary *info))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler{ + if (!asset) { + if (completion) completion(nil, nil); + return -1; + } + if ([[PHCachingImageManager class] instancesRespondToSelector:@selector(requestLivePhotoForAsset:targetSize:contentMode:options:resultHandler:)]) { + PHLivePhotoRequestOptions *livePhotoRequestOptions = [[PHLivePhotoRequestOptions alloc] init]; + livePhotoRequestOptions.networkAccessAllowed = YES; // 允许访问网络 + livePhotoRequestOptions.progressHandler = phProgressHandler; + int32_t imageRequestID = [[[TZImageManager manager] phCachingImageManager] requestLivePhotoForAsset:asset + targetSize:PHImageManagerMaximumSize + contentMode:PHImageContentModeAspectFit + options:livePhotoRequestOptions + resultHandler:^(PHLivePhoto * _Nullable livePhoto, NSDictionary * _Nullable info) { + if (completion) { + completion(livePhoto, info); + } + }]; + return imageRequestID; + }else { + if (completion) completion(nil, nil); + return -1; + } +} + - (UIImage *)getImageWithVideoURL:(NSURL *)videoURL { AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil]; if (!asset) { @@ -1063,6 +1095,13 @@ - (UIImage *)fixOrientation:(UIImage *)aImage { return img; } +- (PHCachingImageManager *)phCachingImageManager { + if (!_phCachingImageManager) { + _phCachingImageManager = [[PHCachingImageManager alloc] init]; + } + return _phCachingImageManager; +} + #pragma clang diagnostic pop @end diff --git a/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto@2x.png b/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto@2x.png new file mode 100644 index 00000000..d63bc403 Binary files /dev/null and b/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto@2x.png differ diff --git a/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto@3x.png b/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto@3x.png new file mode 100644 index 00000000..60bcda38 Binary files /dev/null and b/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto@3x.png differ diff --git a/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto_slash@2x.png b/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto_slash@2x.png new file mode 100644 index 00000000..8c7c60a4 Binary files /dev/null and b/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto_slash@2x.png differ diff --git a/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto_slash@3x.png b/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto_slash@3x.png new file mode 100644 index 00000000..a5045c0c Binary files /dev/null and b/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_livephoto_slash@3x.png differ diff --git a/TZImagePickerController/TZImagePickerController/TZImagePickerController.h b/TZImagePickerController/TZImagePickerController/TZImagePickerController.h index 3ac3946e..77f39dc5 100644 --- a/TZImagePickerController/TZImagePickerController/TZImagePickerController.h +++ b/TZImagePickerController/TZImagePickerController/TZImagePickerController.h @@ -115,6 +115,11 @@ /// 默认为YES,如果设置为NO,用户将不能选择发送图片 @property (nonatomic, assign) BOOL allowPickingImage; +/// Default is NO, if set YES, user can picking liveImage. +/// 默认为NO,如果设置为YES,用户将能选择发送实况图片 +@property (nonatomic, assign) BOOL allowPickingLiveImage; + + /// Default is YES, if set NO, user can't take picture. /// 默认为YES,如果设置为NO, 用户将不能拍摄照片 @property (nonatomic, assign) BOOL allowTakePicture; @@ -383,6 +388,7 @@ + (instancetype)sharedInstance; @property (copy, nonatomic) NSString *preferredLanguage; @property(nonatomic, assign) BOOL allowPickingImage; +@property (nonatomic, assign) BOOL allowPickingLiveImage; @property (nonatomic, assign) BOOL allowPickingVideo; @property (strong, nonatomic) NSBundle *languageBundle; @property (assign, nonatomic) BOOL showSelectedIndex; diff --git a/TZImagePickerController/TZImagePickerController/TZImagePickerController.m b/TZImagePickerController/TZImagePickerController/TZImagePickerController.m index 3c38ce83..234b636c 100644 --- a/TZImagePickerController/TZImagePickerController/TZImagePickerController.m +++ b/TZImagePickerController/TZImagePickerController/TZImagePickerController.m @@ -189,6 +189,7 @@ - (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(N self.allowPickingOriginalPhoto = YES; self.allowPickingVideo = YES; self.allowPickingImage = YES; + self.allowPickingLiveImage = YES; self.allowTakePicture = YES; self.allowTakeVideo = YES; self.videoMaximumDuration = 10 * 60; @@ -521,6 +522,7 @@ - (void)setAllowCrop:(BOOL)allowCrop { if (allowCrop) { // 允许裁剪的时候,不能选原图和GIF self.allowPickingOriginalPhoto = NO; self.allowPickingGif = NO; + self.allowPickingLiveImage = NO; self.photoWidth = 1200; self.photoPreviewMaxWidth = 1200; } @@ -632,6 +634,14 @@ - (void)setAllowPickingImage:(BOOL)allowPickingImage { } } +- (void)setAllowPickingLiveImage:(BOOL)allowPickingLiveImage{ + _allowPickingLiveImage = allowPickingLiveImage; + [TZImagePickerConfig sharedInstance].allowPickingLiveImage = allowPickingLiveImage; + if (!allowPickingLiveImage) { + _allowPickingLiveImage = NO; + } +} + - (void)setAllowPickingVideo:(BOOL)allowPickingVideo { _allowPickingVideo = allowPickingVideo; [TZImagePickerConfig sharedInstance].allowPickingVideo = allowPickingVideo; diff --git a/TZImagePickerController/TZImagePickerController/TZPhotoPickerController.m b/TZImagePickerController/TZImagePickerController/TZPhotoPickerController.m index d60f84a0..7bdd21df 100755 --- a/TZImagePickerController/TZImagePickerController/TZPhotoPickerController.m +++ b/TZImagePickerController/TZImagePickerController/TZPhotoPickerController.m @@ -513,7 +513,13 @@ - (void)doneButtonClick { } [photos replaceObjectAtIndex:i withObject:photo]; } - if (info) [infoArr replaceObjectAtIndex:i withObject:info]; + NSMutableDictionary *infoDict = [NSMutableDictionary dictionary]; + if (info) { + infoDict = [NSMutableDictionary dictionaryWithDictionary:info]; + } + if (model.type == TZAssetModelMediaTypeLivePhoto && model.useLivePhoto) { + [infoDict setObject:@"1" forKey:@"TZ_LIVEPHOTO_USELIVE"]; + } [assets replaceObjectAtIndex:i withObject:model.asset]; for (id item in photos) { if ([item isKindOfClass:[NSNumber class]]) return; } @@ -636,6 +642,7 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell model = _models[indexPath.item - diff];; } cell.allowPickingGif = tzImagePickerVc.allowPickingGif; + cell.allowPickingLiveImage = tzImagePickerVc.allowPickingLiveImage; cell.model = model; if (model.isSelected && tzImagePickerVc.showSelectedIndex) { cell.index = [tzImagePickerVc.selectedAssetIds indexOfObject:model.asset.localIdentifier] + 1; diff --git a/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.h b/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.h index 451d965d..658d742e 100644 --- a/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.h +++ b/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.h @@ -7,6 +7,7 @@ // #import +#import @class TZAssetModel; @interface TZAssetPreviewCell : UICollectionViewCell @@ -74,3 +75,33 @@ @interface TZGifPreviewCell : TZAssetPreviewCell @property (strong, nonatomic) TZPhotoPreviewView *previewView; @end + + +@interface TZLivePhotoPreviewCell : TZAssetPreviewCell + +@property (nonatomic, strong) UIImageView *imageView; +@property (nonatomic, strong) UIScrollView *scrollView; +@property (nonatomic, strong) UIView *imageContainerView; +@property (nonatomic, strong) TZProgressView *progressView; +@property (nonatomic, strong) UIImageView *iCloudErrorIcon; +@property (nonatomic, strong) UILabel *iCloudErrorLabel; +@property (nonatomic, strong) UIButton *useLivePhotoButton; +@property (nonatomic, strong) PHLivePhotoView *livePhotoView; + +@property (nonatomic, copy) void (^iCloudSyncFailedHandle)(id asset, BOOL isSyncFailed); + +@property (nonatomic, strong) id asset; +@property (nonatomic, strong) PHLivePhoto *livePhoto; +@property (nonatomic, copy) void (^imageProgressUpdateBlock)(double progress); + +@property (nonatomic, assign) int32_t imageRequestID; + +@property (nonatomic, assign) BOOL canPlayLivePhoto; + +- (void)recoverSubviews; + +- (void)prepareForDisplay; + +- (void)prepareForHide; + +@end diff --git a/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.m b/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.m index 1c26b7f0..adae5876 100644 --- a/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.m +++ b/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.m @@ -578,3 +578,335 @@ - (void)signleTapAction { } @end + + +@interface TZLivePhotoPreviewCell () + +@property (nonatomic, assign) BOOL isRequestingLive; + + +@end +@implementation TZLivePhotoPreviewCell + +- (void)configSubviews { + + _scrollView = [[UIScrollView alloc] init]; + _scrollView.bouncesZoom = YES; + _scrollView.maximumZoomScale = 4; + _scrollView.minimumZoomScale = 1.0; + _scrollView.multipleTouchEnabled = YES; + _scrollView.delegate = self; + _scrollView.scrollsToTop = NO; + _scrollView.showsHorizontalScrollIndicator = NO; + _scrollView.showsVerticalScrollIndicator = YES; + _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _scrollView.delaysContentTouches = NO; + _scrollView.canCancelContentTouches = YES; + _scrollView.alwaysBounceVertical = NO; + if (@available(iOS 11, *)) { + _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } + [self.contentView addSubview:_scrollView]; + + _imageContainerView = [[UIView alloc] init]; + _imageContainerView.clipsToBounds = YES; + _imageContainerView.contentMode = UIViewContentModeScaleAspectFill; + [_scrollView addSubview:_imageContainerView]; + + _imageView = [[UIImageView alloc] init]; + _imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500]; + _imageView.contentMode = UIViewContentModeScaleAspectFill; + _imageView.clipsToBounds = YES; + [_imageContainerView addSubview:_imageView]; + + _livePhotoView = [[PHLivePhotoView alloc] initWithFrame:CGRectZero]; + _livePhotoView.userInteractionEnabled = NO; + [_imageContainerView addSubview:_livePhotoView]; + + _useLivePhotoButton = [UIButton buttonWithType:UIButtonTypeCustom]; + + UIImage *openImage = [UIImage tz_imageNamedFromMyBundle:@"photo_livephoto"]; + UIImage *closeImage = [UIImage tz_imageNamedFromMyBundle:@"photo_livephoto_slash"]; + if (@available(iOS 13.0, *)) { + UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:13 weight:UIImageSymbolWeightRegular]; + openImage = [[UIImage systemImageNamed:@"livephoto" withConfiguration:config] + imageWithTintColor:[UIColor whiteColor] + renderingMode:UIImageRenderingModeAlwaysOriginal]; + + closeImage = [[UIImage systemImageNamed:@"livephoto.slash" withConfiguration:config] + imageWithTintColor:[UIColor whiteColor] + renderingMode:UIImageRenderingModeAlwaysOriginal]; + + } + [_useLivePhotoButton setImage:openImage forState:UIControlStateNormal]; + [_useLivePhotoButton setImage:closeImage forState:UIControlStateSelected]; + [_useLivePhotoButton setTitle:@" LIVE" forState:UIControlStateNormal]; + [_useLivePhotoButton setTitle:@" 关闭" forState:UIControlStateSelected]; + [_useLivePhotoButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + _useLivePhotoButton.titleLabel.font = [UIFont systemFontOfSize:12]; + [_useLivePhotoButton addTarget:self action:@selector(useLivePhotoButtonClick:) forControlEvents:UIControlEventTouchUpInside]; + _useLivePhotoButton.layer.cornerRadius = 12; + _useLivePhotoButton.layer.masksToBounds = YES; + _useLivePhotoButton.backgroundColor = [UIColor colorWithWhite:0.000 alpha:0.300]; + [_imageContainerView addSubview:_useLivePhotoButton]; + + + _iCloudErrorIcon = [[UIImageView alloc] init]; + _iCloudErrorIcon.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"]; + _iCloudErrorIcon.hidden = YES; + [self.contentView addSubview:_iCloudErrorIcon]; + _iCloudErrorLabel = [[UILabel alloc] init]; + _iCloudErrorLabel.font = [UIFont systemFontOfSize:10]; + _iCloudErrorLabel.textColor = [UIColor whiteColor]; + _iCloudErrorLabel.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"]; + _iCloudErrorLabel.hidden = YES; + [self.contentView addSubview:_iCloudErrorLabel]; + + UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)]; + [self.contentView addGestureRecognizer:tap1]; + UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)]; + tap2.numberOfTapsRequired = 2; + [tap1 requireGestureRecognizerToFail:tap2]; + [self.contentView addGestureRecognizer:tap2]; + + [self configProgressView]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil]; + +} + +- (void)configProgressView { + _progressView = [[TZProgressView alloc] init]; + _progressView.hidden = YES; + [self.contentView addSubview:_progressView]; +} +- (void)setModel:(TZAssetModel *)model { + + [super setModel:model]; + + [_scrollView setZoomScale:1.0 animated:NO]; + + self.canPlayLivePhoto = NO; + [self stopLivePlayback]; + self.imageView.hidden = NO; + self.livePhotoView.hidden = self.useLivePhotoButton.hidden = YES; + self.livePhotoView.livePhoto = nil; + self.imageView.image = nil; + + self.asset = model.asset; +} +- (void)setAsset:(PHAsset *)asset { + + if (_asset && self.imageRequestID) { + [[PHImageManager defaultManager] cancelImageRequest:self.imageRequestID]; + } + + _asset = asset; + + // 先显示缩略图 + [[TZImageManager manager] getPhotoWithAsset:asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { + if (photo) { + self.imageView.image = photo; + } + [self resizeSubviews]; + if (self.isRequestingLive) { + return; + } + // 再显示live图 + self.isRequestingLive = YES; + self.imageRequestID = [[TZImageManager manager] getLivePhotoWithAsset:self.model.asset completion:^(PHLivePhoto *livePhoto, NSDictionary *info) { + self.isRequestingLive = NO; + self.progressView.hidden = YES; + if (!livePhoto){ + self.imageRequestID = 0; + self.livePhotoView.hidden = self.useLivePhotoButton.hidden = YES; + self.imageView.hidden = NO; + return; + } + + self.livePhoto = livePhoto; + self.livePhotoView.livePhoto = self.livePhoto; + + self.livePhotoView.hidden = self.useLivePhotoButton.hidden = NO; + self.useLivePhotoButton.selected = !self.model.useLivePhoto; + self.imageView.hidden = YES; + + [self resizeSubviews]; + + // ✅ 加载完成后,根据当前是否允许播放来决定 + if (self.canPlayLivePhoto && self.model.useLivePhoto) { + [self startLivePlayback]; + } + } withProgressHandler:^(double progress, NSError * _Nullable error, BOOL * _Nonnull stop, NSDictionary * _Nullable info) { + progress = progress > 0.02 ? progress : 0.02; + dispatch_async(dispatch_get_main_queue(), ^{ + BOOL iCloudSyncFailed = [TZCommonTools isICloudSyncError:error]; + self.iCloudErrorLabel.hidden = !iCloudSyncFailed; + self.iCloudErrorIcon.hidden = !iCloudSyncFailed; + if (self.iCloudSyncFailedHandle) { + self.iCloudSyncFailedHandle(asset, iCloudSyncFailed); + } + + self.progressView.progress = progress; + if (progress >= 1) { + self.progressView.hidden = YES; + self.imageRequestID = 0; + } else { + self.progressView.hidden = NO; + } + }); + }]; + } progressHandler:nil networkAccessAllowed:NO]; + + [self configMaximumZoomScale]; +} +- (void)recoverSubviews { + [_scrollView setZoomScale:_scrollView.minimumZoomScale animated:NO]; + [self resizeSubviews]; +} +- (void)resizeSubviews { + _imageContainerView.tz_origin = CGPointZero; + _imageContainerView.tz_width = self.scrollView.tz_width; + + UIImage *image = _imageView.image; + if (image.size.height / image.size.width > self.tz_height / self.scrollView.tz_width) { + CGFloat width = image.size.width / image.size.height * self.scrollView.tz_height; + if (width < 1 || isnan(width)) width = self.tz_width; + width = floor(width); + + _imageContainerView.tz_width = width; + _imageContainerView.tz_height = self.tz_height; + _imageContainerView.tz_centerX = self.scrollView.tz_width / 2; + } else { + CGFloat height = image.size.height / image.size.width * self.scrollView.tz_width; + if (height < 1 || isnan(height)) height = self.tz_height; + height = floor(height); + _imageContainerView.tz_height = height; + _imageContainerView.tz_centerY = self.tz_height / 2; + } + if (_imageContainerView.tz_height > self.tz_height && _imageContainerView.tz_height - self.tz_height <= 1) { + _imageContainerView.tz_height = self.tz_height; + } + CGFloat contentSizeH = MAX(_imageContainerView.tz_height, self.tz_height); + _scrollView.contentSize = CGSizeMake(self.scrollView.tz_width, contentSizeH); + [_scrollView scrollRectToVisible:self.bounds animated:NO]; + _scrollView.alwaysBounceVertical = _imageContainerView.tz_height <= self.tz_height ? NO : YES; + _imageView.frame = _imageContainerView.bounds; + _livePhotoView.frame = _imageContainerView.bounds; +} + +- (void)configMaximumZoomScale { + + _scrollView.maximumZoomScale = 4.0; + + if ([self.asset isKindOfClass:[PHAsset class]]) { + PHAsset *phAsset = (PHAsset *)self.asset; + CGFloat aspectRatio = phAsset.pixelWidth / (CGFloat)phAsset.pixelHeight; + // 优化超宽图片的显示 + if (aspectRatio > 1.5) { + self.scrollView.maximumZoomScale *= aspectRatio / 1.5; + } + } +} + +- (void)layoutSubviews { + [super layoutSubviews]; + _scrollView.frame = CGRectMake(10, 0, self.tz_width - 20, self.tz_height); + static CGFloat progressWH = 40; + CGFloat progressX = (self.tz_width - progressWH) / 2; + CGFloat progressY = (self.tz_height - progressWH) / 2; + _progressView.frame = CGRectMake(progressX, progressY, progressWH, progressWH); + [self recoverSubviews]; + _iCloudErrorIcon.frame = CGRectMake(20, [TZCommonTools tz_statusBarHeight] + 44 + 10, 28, 28); + _iCloudErrorLabel.frame = CGRectMake(53, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.tz_width - 63, 28); + CGFloat MinY = 0; + CGFloat imageContainerMinY = CGRectGetMinY(self.imageContainerView.frame); + if (imageContainerMinY < [TZCommonTools tz_statusBarHeight] + 44) { + MinY = ([TZCommonTools tz_statusBarHeight] + 44) - imageContainerMinY; + } + self.useLivePhotoButton.frame = CGRectMake(CGRectGetMinX(self.livePhotoView.frame) + 16, MinY + 16, 56, 24); +} +#pragma mark - UITapGestureRecognizer Event +- (void)doubleTap:(UITapGestureRecognizer *)tap { + if (_scrollView.zoomScale > _scrollView.minimumZoomScale) { + _scrollView.contentInset = UIEdgeInsetsZero; + [_scrollView setZoomScale:_scrollView.minimumZoomScale animated:YES]; + } else { + CGPoint touchPoint = [tap locationInView:self.imageView]; + CGFloat newZoomScale = MIN(_scrollView.maximumZoomScale, 2.5); + CGFloat xsize = self.frame.size.width / newZoomScale; + CGFloat ysize = self.frame.size.height / newZoomScale; + [_scrollView zoomToRect:CGRectMake(touchPoint.x - xsize/2, touchPoint.y - ysize/2, xsize, ysize) animated:YES]; + } +} + +- (void)singleTap:(UITapGestureRecognizer *)tap { + if (self.singleTapGestureBlock) { + self.singleTapGestureBlock(); + } +} +- (void)prepareForDisplay { + self.canPlayLivePhoto = YES; + [self recoverSubviews]; + if (self.livePhoto && self.model.useLivePhoto) { + [self startLivePlayback]; + } +} + +- (void)prepareForHide { + self.canPlayLivePhoto = NO; + [self stopLivePlayback]; + [self recoverSubviews]; +} + +- (void)startLivePlayback { + if (!self.livePhoto || !self.model.useLivePhoto || !self.canPlayLivePhoto) { + [self stopLivePlayback]; + return; + } + if (self.livePhotoView.livePhoto != self.livePhoto) { + self.livePhotoView.livePhoto = self.livePhoto; + } + // ✅ 播放前加入轻微震动反馈 + if (@available(iOS 10.0, *)) { + UIImpactFeedbackGenerator *feedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; + [feedback prepare]; + [feedback impactOccurred]; + } + [self.livePhotoView startPlaybackWithStyle:PHLivePhotoViewPlaybackStyleFull]; +} + +- (void)useLivePhotoButtonClick:(UIButton *)sender { + self.model.useLivePhoto = !self.model.useLivePhoto; + self.useLivePhotoButton.selected = !self.model.useLivePhoto; + [self startLivePlayback]; +} +- (void)stopLivePlayback { + [self.livePhotoView stopPlayback]; +} +- (void)appWillResignActiveNotification { + [self stopLivePlayback]; +} + +//MARK: UIScrollViewDelegate +- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { + return _imageContainerView; +} +- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { + scrollView.contentInset = UIEdgeInsetsZero; +} +- (void)scrollViewDidZoom:(UIScrollView *)scrollView { + [self refreshImageContainerViewCenter]; +} +- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale { + +} +//MARK: Private +- (void)refreshImageContainerViewCenter { + CGFloat offsetX = (_scrollView.tz_width > _scrollView.contentSize.width) ? ((_scrollView.tz_width - _scrollView.contentSize.width) * 0.5) : 0.0; + CGFloat offsetY = (_scrollView.tz_height > _scrollView.contentSize.height) ? ((_scrollView.tz_height - _scrollView.contentSize.height) * 0.5) : 0.0; + self.imageContainerView.center = CGPointMake(_scrollView.contentSize.width * 0.5 + offsetX, _scrollView.contentSize.height * 0.5 + offsetY); +} + + +@end diff --git a/TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.m b/TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.m index b8edd50a..d731801c 100644 --- a/TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.m +++ b/TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.m @@ -213,7 +213,8 @@ - (void)configCollectionView { [_collectionView registerClass:[TZPhotoPreviewCell class] forCellWithReuseIdentifier:@"TZPhotoPreviewCellGIF"]; [_collectionView registerClass:[TZVideoPreviewCell class] forCellWithReuseIdentifier:@"TZVideoPreviewCell"]; [_collectionView registerClass:[TZGifPreviewCell class] forCellWithReuseIdentifier:@"TZGifPreviewCell"]; - + [_collectionView registerClass:[TZLivePhotoPreviewCell class] forCellWithReuseIdentifier:@"TZLivePhotoPreviewCell"]; + TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController; if (_tzImagePickerVc.scaleAspectFillCrop && _tzImagePickerVc.allowCrop) { _collectionView.scrollEnabled = NO; @@ -522,6 +523,13 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell model.iCloudFailed = isSyncFailed; [weakSelf didICloudSyncStatusChanged:model]; }; + }else if (model.type == TZAssetModelMediaTypeLivePhoto && _tzImagePickerVc.allowPickingLiveImage) { + cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZLivePhotoPreviewCell" forIndexPath:indexPath]; + TZLivePhotoPreviewCell *currentCell = (TZLivePhotoPreviewCell *)cell; + currentCell.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) { + model.iCloudFailed = isSyncFailed; + [weakSelf didICloudSyncStatusChanged:model]; + }; } else { NSString *reuseId = model.type == TZAssetModelMediaTypePhotoGif ? @"TZPhotoPreviewCellGIF" : @"TZPhotoPreviewCell"; cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseId forIndexPath:indexPath]; @@ -564,6 +572,8 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { if ([cell isKindOfClass:[TZPhotoPreviewCell class]]) { [(TZPhotoPreviewCell *)cell recoverSubviews]; + }else if ([cell isKindOfClass:[TZLivePhotoPreviewCell class]]) { + [(TZLivePhotoPreviewCell *)cell prepareForDisplay]; } } @@ -575,6 +585,8 @@ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:( if (videoCell.player && videoCell.player.rate != 0.0) { [videoCell pausePlayerAndShowNaviBar]; } + } else if ([cell isKindOfClass:[TZLivePhotoPreviewCell class]]) { + [(TZLivePhotoPreviewCell *)cell prepareForHide]; } }