This commit is contained in:
启星
2025-08-08 10:49:36 +08:00
parent 6400cf78bb
commit b5ce3d580a
8780 changed files with 978183 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
//
// NSBundle+TUIImagePicker.h
// NSBundle+TUIImagePicker
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface NSBundle (TUIImagePicker)
+ (NSBundle *)tz_imagePickerBundle;
+ (NSString *)tui_localizedStringForKey:(NSString *)key value:(NSString *)value;
+ (NSString *)tui_localizedStringForKey:(NSString *)key;
@end

View File

@@ -0,0 +1,34 @@
//
// NSBundle+TUIImagePicker.m
// NSBundle+TUIImagePicker
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import "NSBundle+TUIImagePicker.h"
#import "TUIMultimediaNavController.h"
@implementation NSBundle (TUIImagePicker)
+ (NSBundle *)tz_imagePickerBundle {
#ifdef SWIFT_PACKAGE
NSBundle *bundle = SWIFTPM_MODULE_BUNDLE;
#else
NSBundle *bundle = [NSBundle bundleForClass:[TUIMultimediaNavController class]];
#endif
NSURL *url = [bundle URLForResource:@"TUIMultimediaPicker" withExtension:@"bundle"];
bundle = [NSBundle bundleWithURL:url];
return bundle;
}
+ (NSString *)tui_localizedStringForKey:(NSString *)key {
return [self tui_localizedStringForKey:key value:@""];
}
+ (NSString *)tui_localizedStringForKey:(NSString *)key value:(NSString *)value {
NSBundle *bundle = [TUIImagePickerConfig sharedInstance].languageBundle;
NSString *value1 = [bundle localizedStringForKey:key value:value table:nil];
return value1;
}
@end

View File

@@ -0,0 +1,26 @@
//
// TUIMultimediaAlbumPicker.h
// TUIMultimediaPlugin
//
// Created by yiliangwang on 2024/11/5.
//
#import <Foundation/Foundation.h>
#import "TUIMultimediaNavController.h"
#import "TUIMultimediaProcessor.h"
#import <TUIChat/TUIBaseChatViewController.h>
#import <TUIChat/TUIVideoMessageCellData.h>
#import <TUICore/TUICore.h>
#import <TUICore/TUIDefine.h>
#import <TUICore/TUIThemeManager.h>
#import <TUIChat/AlbumPicker.h>
#import <TUIMultimediaCore/TUIImageManager.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIMultimediaAlbumPicker : NSObject <IAlbumPicker>
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,281 @@
//
// TUIMultimediaAlbumPicker.m
// TUIMultimediaPlugin
//
// Created by yiliangwang on 2024/11/5.
//
#import "TUIMultimediaAlbumPicker.h"
@implementation TUIMultimediaAlbumPicker
- (void)pickMediaWithCaller:(UIViewController *)caller originalMediaPicked:(IAlbumPickerCallback)mediaPicked progressCallback:(IAlbumPickerCallback)progressCallback finishedCallback:(IAlbumPickerCallback)finishedCallback
{
UIViewController * pushVC = caller;
TUIMultimediaNavController *imagePickerVc = [self createImagePickerVCoriginalMediaPicked:mediaPicked progressCallback:progressCallback finishedCallback:finishedCallback];
if (pushVC && imagePickerVc) {
[pushVC presentViewController:imagePickerVc animated:YES completion:nil];
}
}
- (TUIMultimediaNavController *)createImagePickerVCoriginalMediaPicked:(IAlbumPickerCallback)originalMediaPicked
progressCallback:(IAlbumPickerCallback)progressCallback
finishedCallback:(IAlbumPickerCallback)finishedCallback {
TUIMultimediaNavController *imagePickerVc = [[TUIMultimediaNavController alloc] initWithMaxImagesCount:9 delegate:(id)self];
imagePickerVc.modalPresentationStyle = 0;
@weakify(self)
[imagePickerVc setDidFinishPickingHandle:^(NSArray<TUIAssetPickModel *> *models, BOOL isSelectOriginalPhoto) {
@strongify(self)
__block BOOL hasFileSizeExceed = NO;
__block BOOL hasImageSizeExceed = NO;
dispatch_group_t group = dispatch_group_create();
for (TUIAssetPickModel *model in models) {
dispatch_group_enter(group);
if ([[TUIImageManager defaultManager] isVideo:model.asset]) {
[self handleVideoAsset:model
originalMediaPicked:^(NSDictionary *param) {
if (originalMediaPicked) {
originalMediaPicked(param);
}
}
progressCallback:^(NSDictionary *param) {
if (progressCallback) {
progressCallback(param);
}
}
finishedCallback:^(NSDictionary *param) {
if (finishedCallback) {
finishedCallback(param);
}
}
completion:^(BOOL limitedFlag) {
if (limitedFlag) {
hasFileSizeExceed = YES;
}
dispatch_group_leave(group);
}];
} else {
[self handleImageAsset:model
isSelectOriginalPhoto:isSelectOriginalPhoto
originalMediaPicked:^(NSDictionary *param) {
if (originalMediaPicked) {
originalMediaPicked(param);
}
} progressCallback:^(NSDictionary *param) {
if (progressCallback) {
progressCallback(param);
}
} finishedCallback:^(NSDictionary *param) {
if (finishedCallback) {
finishedCallback(param);
}
} completion:^(BOOL limitedFlag) {
if (limitedFlag) {
hasImageSizeExceed = YES;
}
dispatch_group_leave(group);
}];
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if (hasFileSizeExceed || hasImageSizeExceed) {
[self showFileCheckToast:YES];
}
});
}];
return imagePickerVc;
}
- (void)handleVideoAsset:(TUIAssetPickModel *)model
originalMediaPicked:(IAlbumPickerCallback)mediaPicked
progressCallback:(IAlbumPickerCallback)progressCallback
finishedCallback:(IAlbumPickerCallback)finishedCallback
completion:(void (^)(BOOL limitedFlag))completion {
if (model.editurl) {
//Edited; no transcoding needed, can be sent directly.
UIImage * snapaImage = [[TUIImageManager defaultManager] getImageWithVideoURL:model.editurl];
NSString *snapshotPath = [[TUIImageManager defaultManager] genImagePath:snapaImage isVideoSnapshot:YES];
NSInteger duration = [[TUIImageManager defaultManager] getDurationWithVideoURL:model.editurl];
V2TIMMessage *message = [[V2TIMManager sharedInstance] createVideoMessage:model.editurl.path type:model.editurl.path.pathExtension duration:(int)duration snapshotPath:snapshotPath];
NSDictionary *param = @{@"message": message,@"type":@"video"};
if (finishedCallback) {
finishedCallback(param);
}
}
else {
[[TUIImageManager defaultManager] getAssetBytes:model.asset completion:^(NSInteger assetBytes) {
// For videos larger than 200MB, directly report a file size exceeded warning and do not compress.
if (assetBytes > 200 * 1024 * 1024) {
completion(YES);
return;
} else {
dispatch_async(dispatch_get_main_queue(), ^{
// Show video snapshot on screen first
NSString *snapshotPath = [[TUIImageManager defaultManager] genImagePath:model.image isVideoSnapshot:YES];
TUIMessageCellData *placeHolderCellData = [TUIVideoMessageCellData placeholderCellDataWithSnapshotUrl:snapshotPath thubImage:model.image];
NSString *random = [NSString stringWithFormat:@"%u", arc4random()];
placeHolderCellData.msgID = [NSString stringWithFormat:@"%@%@", snapshotPath, random];
NSDictionary *param = @{@"placeHolderCellData" : placeHolderCellData,@"type":@"video"};
if (mediaPicked) {
mediaPicked(param);
}
__weak typeof(self)weakSelf = self;
//Video transcoding and compression; replace the placeholder image after completion.
[self getVideoOutputPathWithAsset:model.asset placeHolderCellData:placeHolderCellData
progress:^(NSDictionary *param) {
if (progressCallback) {
progressCallback(param);
}
}
complete:^(NSString *videoPath) {
dispatch_async(dispatch_get_main_queue(), ^{
V2TIMMessage *message = [[V2TIMManager sharedInstance] createVideoMessage:videoPath type:videoPath.pathExtension duration:model.asset.duration snapshotPath:snapshotPath];
NSDictionary *param = @{@"message": message,
@"placeHolderCellData" : placeHolderCellData,
@"type":@"video"};
if (finishedCallback) {
finishedCallback(param);
}
});
} failure:^(NSString *errorMessage, NSError *error) {
// Handle error if needed
}];
});
}
completion(NO);
}];
}
}
- (void)handleImageAsset:(TUIAssetPickModel *)model
isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto
originalMediaPicked:(IAlbumPickerCallback)mediaPicked
progressCallback:(IAlbumPickerCallback)progressCallback
finishedCallback:(IAlbumPickerCallback)finishedCallback
completion:(void (^)(BOOL limitedFlag))completion {
if (model.editImage != nil) {
[self sendImageMessage:model.editImage finishedCallback:finishedCallback];
return;
}
[[TUIImageManager defaultManager] getAssetBytes:model.asset completion:^(NSInteger assetBytes) {
if (assetBytes > 28 * 1024 * 1024) {
completion(YES);
return;
}
if (mediaPicked) {
mediaPicked(nil);
}
if (isSelectOriginalPhoto) {
[[TUIImageManager defaultManager] getOriginalPhotoDataWithAsset:model.asset
progressHandler:nil
completion:^(NSData *data, NSDictionary *info, BOOL isDegraded) {
if (!isDegraded) {
[self sendImageMessage:[UIImage imageWithData:data] finishedCallback:finishedCallback];
}
}];
} else {
[self sendImageMessage:model.image finishedCallback:finishedCallback];
}
}];
}
- (void)sendImageMessage:(UIImage*) image finishedCallback:(IAlbumPickerCallback)finishedCallback{
NSString *imagePath = [[TUIImageManager defaultManager] genImagePath:image isVideoSnapshot:NO];
V2TIMMessage *message = [[V2TIMManager sharedInstance] createImageMessage:imagePath];
NSDictionary *param = @{@"message": message,@"type":@"image"};
if (finishedCallback != nil) {
finishedCallback(param);
}
}
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset placeHolderCellData:(TUIMessageCellData *)placeHolderCellData
progress:(IAlbumPickerCallback)progressHandler
complete:(void(^)(NSString *))completeBlock failure:(void (^)(NSString *errorMessage, NSError *error))failure {
[[TUIImageManager defaultManager] requestVideoURLWithAsset:asset success:^(NSURL *videoURL) {
NSNumber *videoBytes;
[videoURL getResourceValue:&videoBytes forKey:NSURLFileSizeKey error:nil];
// The maximum video size sent by IMSDK is 100M
NSString *presetName = (videoBytes.intValue > 100 * 1024 * 1024 ? AVAssetExportPreset1280x720 : AVAssetExportPresetPassthrough);
if (videoBytes.intValue > 200 * 1024 * 1024) {
NSLog(@"requestVideoURLWithAsset falied, The maximum video size sent by IMSDK is 100M");
if (failure) {
failure(@"The current video size is greater than 200M",nil);
}
return;
}
BOOL suportMultimedia = YES;
if (suportMultimedia) {
[[TUIMultimediaProcessor shareInstance] transcodeVideo:videoURL complete:^(TranscodeResult *result) {
NSLog(@"TUIMultimediaMediaProcessor complete %@ ",result.transcodeUri.path);
NSData *videoData = [NSData dataWithContentsOfURL:result.transcodeUri];
NSString *videoPath = [NSString stringWithFormat:@"%@%@_%u.mp4", TUIKit_Video_Path, [TUITool genVideoName:nil],arc4random()];
[[NSFileManager defaultManager] createFileAtPath:videoPath contents:videoData attributes:nil];
dispatch_async(dispatch_get_main_queue(), ^{
if (completeBlock) {
completeBlock(videoPath);
}
});
} progress:^(float progress) {
NSLog(@"TUIMultimediaMediaProcessor videoTranscodingProgress %f ",progress);
placeHolderCellData.videoTranscodingProgress = progress;
if (progressHandler) {
NSNumber *numberValue = @(progress);
NSDictionary *param = @{
@"progress": numberValue
};
progressHandler(param);
}
} ];
}
else {
[[TUIImageManager defaultManager] getVideoOutputPathWithAsset:asset presetName:presetName progress:^(CGFloat progress) {
placeHolderCellData.videoTranscodingProgress = progress;
} success:^(NSString *outputPath) {
dispatch_async(dispatch_get_main_queue(), ^{
if (completeBlock) {
completeBlock(outputPath);
}
});
} failure:^(NSString *errorMessage, NSError *error) {
NSLog(@"getVideoOutputPathWithAsset falied, errorMessage:%@", errorMessage);
if (failure) {
failure(errorMessage,error);
}
}];
}
} failure:^(NSDictionary *info) {
NSLog(@"requestVideoURLWithAsset falied, errorInfo:%@", info);
if (failure) {
failure(info.description,nil);
}
}];
}
- (void)showFileCheckToast:(BOOL)isFile {
UIAlertController *ac = [UIAlertController alertControllerWithTitle:isFile?
TIMCommonLocalizableString(TUIKitFileSizeCheckLimited):
TIMCommonLocalizableString(TUIKitImageSizeCheckLimited)
message:nil preferredStyle:UIAlertControllerStyleAlert];
[ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(Confirm) style:UIAlertActionStyleDefault handler:nil]];
UIViewController *topViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
}
dispatch_async(dispatch_get_main_queue(), ^{
[topViewController presentViewController:ac animated:YES completion:nil];
});
}
@end

View File

@@ -0,0 +1,107 @@
//
// TUIMultimediaNavController.h
// TUIMultimediaNavController
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <TUIMultimediaCore/TUIAssetModel.h>
#import "NSBundle+TUIImagePicker.h"
#import <TUIMultimediaCore/TUIImageManager.h>
#import "TUIPhotoPreviewController.h"
#import "TUIPhotoPreviewCell.h"
@class TUIAlbumCell, TUIAssetCell;
@protocol TUIMultimediaNavControllerDelegate;
@interface TUIMultimediaNavController : UINavigationController
- (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount delegate:(id<TUIMultimediaNavControllerDelegate>)delegate;
- (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id<TUIMultimediaNavControllerDelegate>)delegate;
- (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id<TUIMultimediaNavControllerDelegate>)delegate pushPhotoPickerVc:(BOOL)pushPhotoPickerVc;
#pragma mark -
/// Maximum number of photos allowed to be selected (Default is 9)
@property (nonatomic, assign) NSInteger maxImagesCount;
/// The number of photos displayed in each row (Default is 4)
@property (nonatomic, assign) NSInteger columnNumber;
/// Default is 600px
@property (nonatomic, assign) CGFloat photoPreviewMaxWidth;
/// The photos user have selected
@property (nonatomic, strong) NSMutableArray *selectedAssets;
@property (nonatomic, strong) NSMutableArray<TUIAssetModel *> *selectedModels;
@property (nonatomic, strong) NSMutableArray *selectedAssetIds;
- (void)addSelectedModel:(TUIAssetModel *)model;
- (void)removeSelectedModel:(TUIAssetModel *)model;
- (UIAlertController *)showAlertWithTitle:(NSString *)title;
- (void)showProgressHUD;
- (void)hideProgressHUD;
@property (nonatomic, assign) BOOL isSelectOriginalPhoto;
@property (assign, nonatomic) BOOL needShowStatusBar;
@property (nonatomic, copy) NSString *takePictureImageName;
@property (nonatomic, copy) NSString *photoSelImageName;
@property (nonatomic, copy) NSString *photoDefImageName;
@property (nonatomic, copy) NSString *photoOriginSelImageName;
@property (nonatomic, copy) NSString *photoOriginDefImageName;
@property (nonatomic, copy) NSString *photoPreviewOriginDefImageName;
@property (nonatomic, copy) NSString *photoNumberIconImageName;
@property (nonatomic, strong) UIImage *takePictureImage;
@property (nonatomic, strong) UIImage *addMorePhotoImage;
@property (nonatomic, strong) UIImage *photoSelImage;
@property (nonatomic, strong) UIImage *photoDefImage;
@property (nonatomic, strong) UIImage *photoOriginSelImage;
@property (nonatomic, strong) UIImage *photoOriginDefImage;
@property (nonatomic, strong) UIImage *photoPreviewOriginDefImage;
@property (nonatomic, strong) UIImage *photoNumberIconImage;
@property (nonatomic, strong) UIColor *oKButtonTitleColorNormal;
@property (nonatomic, strong) UIColor *oKButtonTitleColorDisabled;
@property (nonatomic, strong) UIColor *naviBgColor;
@property (nonatomic, strong) UIColor *naviTitleColor;
@property (nonatomic, strong) UIFont *naviTitleFont;
@property (nonatomic, strong) UIColor *barItemTextColor;
@property (nonatomic, strong) UIFont *barItemTextFont;
@property (nonatomic, copy) NSString *doneBtnTitleStr;
@property (nonatomic, copy) NSString *cancelBtnTitleStr;
@property (nonatomic, copy) NSString *previewBtnTitleStr;
@property (nonatomic, copy) NSString *editImageBtnTitleStr;
@property (nonatomic, copy) NSString *fullImageBtnTitleStr;
@property (nonatomic, copy) NSString *settingBtnTitleStr;
@property (nonatomic, copy) NSString *processHintStr;
@property (nonatomic, copy) NSString *editBtnTitleStr;
@property (nonatomic, copy) NSString *editViewCancelBtnTitleStr;
@property (strong, nonatomic) UIColor *iconThemeColor;
@property (nonatomic, copy) void (^didFinishPickingHandle)(NSArray<TUIAssetPickModel *> *models, BOOL isSelectOriginalPhoto);
@property (nonatomic, copy) void (^didFinishPickingPhotosWithInfosHandle)(NSArray<TUIAssetPickModel *> *models,BOOL isSelectOriginalPhoto,NSArray<NSDictionary *> *infos);
- (void)onCancelButtonClick;
@end
@interface UIImage (MyBundle)
+ (UIImage *)tui_imageNamedFromMyBundle:(NSString *)name;
@end
@interface TUICommonTools : NSObject
+ (UIEdgeInsets)tui_safeAreaInsets;
+ (BOOL)tui_isIPhoneX;
+ (BOOL)tui_isLandscape;
+ (CGFloat)tui_statusBarHeight;
+ (NSDictionary *)tui_getInfoDictionary;
+ (NSString *)tui_getAppName;
+ (BOOL)tui_isRightToLeftLayout;
+ (void)configBarButtonItem:(UIBarButtonItem *)item multiMediaNavVC:(TUIMultimediaNavController *)multiMediaNavVC;
+ (BOOL)isICloudSyncError:(NSError *)error;
@end
@interface TUIImagePickerConfig : NSObject
+ (instancetype)sharedInstance;
@property (copy, nonatomic) NSString *preferredLanguage;
@property (strong, nonatomic) NSBundle *languageBundle;
@end

View File

@@ -0,0 +1,698 @@
//
// TUIMultimediaNavController.m
// TUIMultimediaNavController
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import "TUIMultimediaNavController.h"
#import "TUIPhotoPickerController.h"
#import "TUIPhotoPreviewController.h"
#import <TUIMultimediaCore/TUIAssetModel.h>
#import <TUIMultimediaCore/TUIAssetCell.h>
#import <TUICore/UIView+TUILayout.h>
#import <TUIMultimediaCore/TUIImageManager.h>
#import <TIMCommon/TIMDefine.h>
@interface TUIMultimediaNavController () {
NSTimer *_timer;
UILabel *_tipLabel;
UIButton *_settingBtn;
BOOL _pushPhotoPickerVc;
BOOL _didPushPhotoPickerVc;
CGRect _cropRect;
UIButton *_progressHUD;
UIView *_HUDContainer;
UIActivityIndicatorView *_HUDIndicatorView;
UILabel *_HUDLabel;
UIStatusBarStyle _originStatusBarStyle;
}
@property (nonatomic, assign) NSInteger timeout;
@property (nonatomic, assign) NSInteger HUDTimeoutCount;
@end
@implementation TUIMultimediaNavController
- (instancetype)init {
self = [super init];
if (self) {
self = [self initWithMaxImagesCount:9 delegate:nil];
}
return self;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (void)viewDidLoad {
[super viewDidLoad];
self.needShowStatusBar = ![UIApplication sharedApplication].statusBarHidden;
if (@available(iOS 13.0, *)) {
self.view.backgroundColor = UIColor.tertiarySystemBackgroundColor;
} else {
self.view.backgroundColor = [UIColor whiteColor];
}
self.navigationBar.barStyle = UIBarStyleBlack;
self.navigationBar.translucent = YES;
[TUIImageManager defaultManager].shouldFixOrientation = NO;
self.oKButtonTitleColorNormal = TIMCommonDynamicColor(@"primary_theme_color", @"#147AFF");
self.oKButtonTitleColorDisabled = [TIMCommonDynamicColor(@"primary_theme_color", @"#147AFF") colorWithAlphaComponent:0.5];
self.navigationBar.barTintColor = [UIColor colorWithRed:(34/255.0) green:(34/255.0) blue:(34/255.0) alpha:1.0];
self.navigationBar.tintColor = [UIColor whiteColor];
self.automaticallyAdjustsScrollViewInsets = NO;
if (self.needShowStatusBar) [UIApplication sharedApplication].statusBarHidden = NO;
}
- (void)setNaviBgColor:(UIColor *)naviBgColor {
_naviBgColor = naviBgColor;
self.navigationBar.barTintColor = naviBgColor;
[self configNavigationBarAppearance];
}
- (void)setNaviTitleColor:(UIColor *)naviTitleColor {
_naviTitleColor = naviTitleColor;
[self configNaviTitleAppearance];
}
- (void)setNaviTitleFont:(UIFont *)naviTitleFont {
_naviTitleFont = naviTitleFont;
[self configNaviTitleAppearance];
}
- (void)configNaviTitleAppearance {
NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];
if (self.naviTitleColor) {
textAttrs[NSForegroundColorAttributeName] = self.naviTitleColor;
}
if (self.naviTitleFont) {
textAttrs[NSFontAttributeName] = self.naviTitleFont;
}
self.navigationBar.titleTextAttributes = textAttrs;
[self configNavigationBarAppearance];
}
- (void)configNavigationBarAppearance {
if (@available(iOS 13.0, *)) {
UINavigationBarAppearance *barAppearance = [[UINavigationBarAppearance alloc] init];
if (self.navigationBar.isTranslucent) {
UIColor *barTintColor = self.navigationBar.barTintColor;
barAppearance.backgroundColor = [barTintColor colorWithAlphaComponent:0.85];
} else {
barAppearance.backgroundColor = self.navigationBar.barTintColor;
}
barAppearance.titleTextAttributes = self.navigationBar.titleTextAttributes;
self.navigationBar.standardAppearance = barAppearance;
self.navigationBar.scrollEdgeAppearance = barAppearance;
}
}
- (void)setBarItemTextFont:(UIFont *)barItemTextFont {
_barItemTextFont = barItemTextFont;
[self configBarButtonItemAppearance];
}
- (void)setBarItemTextColor:(UIColor *)barItemTextColor {
_barItemTextColor = barItemTextColor;
[self configBarButtonItemAppearance];
}
- (void)configBarButtonItemAppearance {
UIBarButtonItem *barItem;
if (@available(iOS 9, *)) {
barItem = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[TUIMultimediaNavController class]]];
} else {
barItem = [UIBarButtonItem appearanceWhenContainedIn:[TUIMultimediaNavController class], nil];
}
NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];
textAttrs[NSForegroundColorAttributeName] = self.barItemTextColor;
textAttrs[NSFontAttributeName] = self.barItemTextFont;
[barItem setTitleTextAttributes:textAttrs forState:UIControlStateNormal];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
_originStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
[self configNavigationBarAppearance];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[UIApplication sharedApplication].statusBarStyle = _originStatusBarStyle;
[self hideProgressHUD];
}
- (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount delegate:(id<TUIMultimediaNavControllerDelegate>)delegate {
return [self initWithMaxImagesCount:maxImagesCount columnNumber:4 delegate:delegate pushPhotoPickerVc:YES];
}
- (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id<TUIMultimediaNavControllerDelegate>)delegate {
return [self initWithMaxImagesCount:maxImagesCount columnNumber:columnNumber delegate:delegate pushPhotoPickerVc:YES];
}
- (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id<TUIMultimediaNavControllerDelegate>)delegate pushPhotoPickerVc:(BOOL)pushPhotoPickerVc {
_pushPhotoPickerVc = pushPhotoPickerVc;
// TUIAlbumPickerController *albumPickerVc = [[TUIAlbumPickerController alloc] init];
// albumPickerVc.isFirstAppear = YES;
// albumPickerVc.columnNumber = columnNumber;
self = [super initWithRootViewController:[UIViewController new]];
if (self) {
self.maxImagesCount = maxImagesCount > 0 ? maxImagesCount : 9; // Default is 9 / 9
self.selectedAssets = [NSMutableArray array];
self.columnNumber = columnNumber;
[self configDefaultSetting];
if (![[TUIImageManager defaultManager] authorizationStatusAuthorized]) {
_tipLabel = [[UILabel alloc] init];
_tipLabel.frame = CGRectMake(8, 120, self.view.mm_w - 16, 60);
_tipLabel.textAlignment = NSTextAlignmentCenter;
_tipLabel.numberOfLines = 0;
_tipLabel.font = [UIFont systemFontOfSize:16];
_tipLabel.textColor = [UIColor blackColor];
_tipLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
NSString *appName = [TUICommonTools tui_getAppName];
NSString *tipText = [NSString stringWithFormat:[NSBundle tui_localizedStringForKey:@"Allow %@ to access your album in \"Settings -> Privacy -> Photos\""],appName];
_tipLabel.text = tipText;
[self.view addSubview:_tipLabel];
_settingBtn = [UIButton buttonWithType:UIButtonTypeSystem];
[_settingBtn setTitle:self.settingBtnTitleStr forState:UIControlStateNormal];
_settingBtn.frame = CGRectMake(0, 180, self.view.mm_w, 44);
_settingBtn.titleLabel.font = [UIFont systemFontOfSize:18];
[_settingBtn addTarget:self action:@selector(settingBtnClick) forControlEvents:UIControlEventTouchUpInside];
_settingBtn.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.view addSubview:_settingBtn];
if ([PHPhotoLibrary authorizationStatus] == 0) {
_timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(observeAuthrizationStatusChange) userInfo:nil repeats:NO];
}
} else {
[self pushPhotoPickerVc];
}
}
return self;
}
- (void)configDefaultSetting {
self.timeout = 30;
self.photoPreviewMaxWidth = 600;
self.naviTitleColor = [UIColor whiteColor];
self.naviTitleFont = [UIFont systemFontOfSize:17];
self.barItemTextFont = [UIFont systemFontOfSize:15];
self.barItemTextColor = [UIColor whiteColor];
self.iconThemeColor = TIMCommonDynamicColor(@"primary_theme_color", @"#147AFF");
[self configDefaultBtnTitle];
}
- (void)configDefaultImageName {
self.takePictureImageName = @"takePicture80";
self.photoDefImage = [self createHollowCircleWithColor:UIColor.whiteColor size:CGSizeMake(24, 24) lineWidth:0.6];
UIImage *baseImage = [self createImageWithColor:nil size:CGSizeMake(24, 24) radius:12];
UIImage *overlayImage = [UIImage tui_imageNamedFromMyBundle:@"selected_tick_icon"];;
UIImage *combinedImage = [self overlayImage:baseImage withImage:overlayImage];
self.photoSelImage = combinedImage;
self.photoNumberIconImage = [self createImageWithColor:nil size:CGSizeMake(24, 24) radius:12]; // @"photo_number_icon";
self.photoPreviewOriginDefImageName = @"preview_original_def";
self.photoOriginDefImageName = @"photo_original_def";
self.photoOriginSelImageName = @"photo_original_sel";
self.photoOriginSelImage = [self resizeImage:combinedImage toSize:CGSizeMake(20, 20)];
self.addMorePhotoImage = [UIImage tui_imageNamedFromMyBundle:@"addMore"];
}
- (void)setTakePictureImageName:(NSString *)takePictureImageName {
_takePictureImageName = takePictureImageName;
_takePictureImage = [UIImage tui_imageNamedFromMyBundle:takePictureImageName];
}
- (void)setPhotoSelImageName:(NSString *)photoSelImageName {
_photoSelImageName = photoSelImageName;
_photoSelImage = [UIImage tui_imageNamedFromMyBundle:photoSelImageName];
}
- (void)setPhotoDefImageName:(NSString *)photoDefImageName {
_photoDefImageName = photoDefImageName;
_photoDefImage = [UIImage tui_imageNamedFromMyBundle:photoDefImageName];
}
- (void)setPhotoNumberIconImageName:(NSString *)photoNumberIconImageName {
_photoNumberIconImageName = photoNumberIconImageName;
_photoNumberIconImage = [UIImage tui_imageNamedFromMyBundle:photoNumberIconImageName];
}
- (void)setPhotoPreviewOriginDefImageName:(NSString *)photoPreviewOriginDefImageName {
_photoPreviewOriginDefImageName = photoPreviewOriginDefImageName;
_photoPreviewOriginDefImage = [UIImage tui_imageNamedFromMyBundle:photoPreviewOriginDefImageName];
}
- (void)setPhotoOriginDefImageName:(NSString *)photoOriginDefImageName {
_photoOriginDefImageName = photoOriginDefImageName;
_photoOriginDefImage = [UIImage tui_imageNamedFromMyBundle:photoOriginDefImageName];
}
- (void)setPhotoOriginSelImageName:(NSString *)photoOriginSelImageName {
_photoOriginSelImageName = photoOriginSelImageName;
_photoOriginSelImage = [UIImage tui_imageNamedFromMyBundle:photoOriginSelImageName];
}
- (void)setTakePictureImage:(UIImage *)takePictureImage {
_takePictureImage = takePictureImage;
_takePictureImageName = @"";
}
- (void)setIconThemeColor:(UIColor *)iconThemeColor {
_iconThemeColor = iconThemeColor;
[self configDefaultImageName];
}
- (void)configDefaultBtnTitle {
self.doneBtnTitleStr = [NSBundle tui_localizedStringForKey:@"Done"];
self.cancelBtnTitleStr = [NSBundle tui_localizedStringForKey:@"Cancel"];
self.previewBtnTitleStr = [NSBundle tui_localizedStringForKey:@"Preview"];
self.editImageBtnTitleStr = [NSBundle tui_localizedStringForKey:@"Edit"];
self.fullImageBtnTitleStr = [NSBundle tui_localizedStringForKey:@"Full image"];
self.settingBtnTitleStr = [NSBundle tui_localizedStringForKey:@"Setting"];
self.processHintStr = [NSBundle tui_localizedStringForKey:@"Processing..."];
self.editBtnTitleStr = [NSBundle tui_localizedStringForKey:@"Edit"];
}
- (void)observeAuthrizationStatusChange {
[_timer invalidate];
_timer = nil;
if ([PHPhotoLibrary authorizationStatus] == 0) {
_timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(observeAuthrizationStatusChange) userInfo:nil repeats:NO];
}
if ([[TUIImageManager defaultManager] authorizationStatusAuthorized]) {
[_tipLabel removeFromSuperview];
[_settingBtn removeFromSuperview];
[self pushPhotoPickerVc];
}
}
- (void)pushPhotoPickerVc {
_didPushPhotoPickerVc = NO;
if (!_didPushPhotoPickerVc && _pushPhotoPickerVc) {
TUIPhotoPickerController *photoPickerVc = [[TUIPhotoPickerController alloc] init];
photoPickerVc.isFirstAppear = YES;
photoPickerVc.columnNumber = self.columnNumber;
[[TUIImageManager defaultManager] getCameraRollAlbum:NO completion:^(TUIAlbumModel *model) {
photoPickerVc.albumModel = model;
[self pushViewController:photoPickerVc animated:YES];
self->_didPushPhotoPickerVc = YES;
}];
}
}
- (UIAlertController *)showAlertWithTitle:(NSString *)title {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:[NSBundle tui_localizedStringForKey:@"OK"] style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
return alertController;
}
- (void)showProgressHUD {
if (!_progressHUD) {
_progressHUD = [UIButton buttonWithType:UIButtonTypeCustom];
[_progressHUD setBackgroundColor:[UIColor clearColor]];
_HUDContainer = [[UIView alloc] init];
_HUDContainer.layer.cornerRadius = 8;
_HUDContainer.clipsToBounds = YES;
_HUDContainer.backgroundColor = [UIColor darkGrayColor];
_HUDContainer.alpha = 0.7;
_HUDIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
_HUDLabel = [[UILabel alloc] init];
_HUDLabel.textAlignment = NSTextAlignmentCenter;
_HUDLabel.text = self.processHintStr;
_HUDLabel.font = [UIFont systemFontOfSize:15];
_HUDLabel.textColor = [UIColor whiteColor];
[_HUDContainer addSubview:_HUDLabel];
[_HUDContainer addSubview:_HUDIndicatorView];
[_progressHUD addSubview:_HUDContainer];
}
[_HUDIndicatorView startAnimating];
UIWindow *applicationWindow;
if ([[[UIApplication sharedApplication] delegate] respondsToSelector:@selector(window)]) {
applicationWindow = [[[UIApplication sharedApplication] delegate] window];
} else {
applicationWindow = [[UIApplication sharedApplication] keyWindow];
}
[applicationWindow addSubview:_progressHUD];
[self.view setNeedsLayout];
self.HUDTimeoutCount++;
@weakify(self)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@strongify(self)
self.HUDTimeoutCount--;
if (self.HUDTimeoutCount <= 0) {
self.HUDTimeoutCount = 0;
[self hideProgressHUD];
}
});
}
- (void)hideProgressHUD {
if (_progressHUD) {
[_HUDIndicatorView stopAnimating];
[_progressHUD removeFromSuperview];
}
}
- (void)setMaxImagesCount:(NSInteger)maxImagesCount {
_maxImagesCount = maxImagesCount;
}
- (void)setTimeout:(NSInteger)timeout {
_timeout = timeout;
if (timeout < 5) {
_timeout = 5;
} else if (_timeout > 600) {
_timeout = 600;
}
}
- (void)setColumnNumber:(NSInteger)columnNumber {
_columnNumber = columnNumber;
if (columnNumber <= 2) {
_columnNumber = 2;
} else if (columnNumber >= 6) {
_columnNumber = 6;
}
// TUIAlbumPickerController *albumPickerVc = [self.childViewControllers firstObject];
// albumPickerVc.columnNumber = _columnNumber;
[TUIImageManager defaultManager].columnNumber = _columnNumber;
}
- (void)setPhotoPreviewMaxWidth:(CGFloat)photoPreviewMaxWidth {
_photoPreviewMaxWidth = photoPreviewMaxWidth;
if (photoPreviewMaxWidth > 800) {
_photoPreviewMaxWidth = 800;
} else if (photoPreviewMaxWidth < 500) {
_photoPreviewMaxWidth = 500;
}
[TUIImageManager defaultManager].photoPreviewMaxWidth = _photoPreviewMaxWidth;
}
- (void)setSelectedAssets:(NSMutableArray *)selectedAssets {
_selectedAssets = selectedAssets;
_selectedModels = [NSMutableArray array];
_selectedAssetIds = [NSMutableArray array];
for (PHAsset *asset in selectedAssets) {
TUIAssetModel *model = [TUIAssetModel modelWithAsset:asset type:[[TUIImageManager defaultManager] getAssetMediaType:asset]];
model.isSelected = YES;
[self addSelectedModel:model];
}
}
- (void)settingBtnClick {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
viewController.automaticallyAdjustsScrollViewInsets = NO;
[super pushViewController:viewController animated:animated];
}
- (UIImage *)createImageWithColor:(UIColor *)color size:(CGSize)size radius:(CGFloat)radius {
if (!color) {
color = self.iconThemeColor;
}
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius];
CGContextAddPath(context, path.CGPath);
CGContextFillPath(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)overlayImage:(UIImage *)baseImage withImage:(UIImage *)overlayImage {
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:baseImage.size];
UIImage *resultImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull context) {
[baseImage drawInRect:CGRectMake(0, 0, baseImage.size.width, baseImage.size.height)];
CGFloat overlayX = (baseImage.size.width - overlayImage.size.width) / 2.0;
CGFloat overlayY = (baseImage.size.height - overlayImage.size.height) / 2.0;
CGPoint overlayPosition = CGPointMake(overlayX, overlayY);
[overlayImage drawAtPoint:overlayPosition];
}];
return resultImage;
}
- (UIImage *)createHollowCircleWithColor:(UIColor *)color size:(CGSize)size lineWidth:(CGFloat)lineWidth {
if (!color) {
color = [UIColor blackColor];
}
CGFloat diameter = MIN(size.width, size.height);
CGFloat scale = [UIScreen mainScreen].scale;
CGSize scaledSize = CGSizeMake(diameter * scale, diameter * scale);
UIGraphicsBeginImageContextWithOptions(scaledSize, NO, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShouldAntialias(context, YES);
CGContextClearRect(context, CGRectMake(0, 0, scaledSize.width, scaledSize.height));
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetLineWidth(context, lineWidth * scale);
CGRect scaledRect = CGRectMake(0.0f, 0.0f, scaledSize.width, scaledSize.height);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:scaledRect];
CGContextAddPath(context, path.CGPath);
CGContextStrokePath(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)resizeImage:(UIImage *)image toSize:(CGSize)newSize {
UIGraphicsBeginImageContextWithOptions(newSize, NO, [UIScreen mainScreen].scale);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
#pragma mark - UIContentContainer
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.02 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (![UIApplication sharedApplication].statusBarHidden) {
if (self.needShowStatusBar) [UIApplication sharedApplication].statusBarHidden = NO;
}
});
}
#pragma mark - Layout
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGFloat progressHUDY = CGRectGetMaxY(self.navigationBar.frame);
_progressHUD.frame = CGRectMake(0, progressHUDY, self.view.mm_w, self.view.mm_h - progressHUDY);
_HUDContainer.frame = CGRectMake((self.view.mm_w - 120) / 2, (_progressHUD.mm_h - 90 - progressHUDY) / 2, 120, 90);
_HUDIndicatorView.frame = CGRectMake(45, 15, 30, 30);
_HUDLabel.frame = CGRectMake(0,40, 120, 50);
}
#pragma mark - Public
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAll;
}
- (void)onCancelButtonClick {
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
#pragma clang diagnostic pop
@implementation UIImage (MyBundle)
+ (UIImage *)tui_imageNamedFromMyBundle:(NSString *)name {
NSBundle *imageBundle = [NSBundle tz_imagePickerBundle];
name = [name stringByAppendingString:@"@2x"];
NSString *imagePath = [imageBundle pathForResource:name ofType:@"png"];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
if (!image) {
// Compatible with the way the business side sets images
name = [name stringByReplacingOccurrencesOfString:@"@2x" withString:@""];
image = [UIImage imageNamed:name];
}
return image;
}
@end
@implementation TUICommonTools
+ (UIEdgeInsets)tui_safeAreaInsets {
UIWindow *window = [UIApplication sharedApplication].windows.firstObject;
if (![window isKeyWindow]) {
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
if (CGRectEqualToRect(keyWindow.bounds, [UIScreen mainScreen].bounds)) {
window = keyWindow;
}
}
if (@available(iOS 11.0, *)) {
UIEdgeInsets insets = [window safeAreaInsets];
return insets;
}
return UIEdgeInsetsZero;
}
+ (BOOL)tui_isIPhoneX {
if ([UIWindow instancesRespondToSelector:@selector(safeAreaInsets)]) {
return [self tui_safeAreaInsets].bottom > 0;
}
return (CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(375, 812)) ||
CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(812, 375)) ||
CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(414, 896)) ||
CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(896, 414)) ||
CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(390, 844)) ||
CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(844, 390)) ||
CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(428, 926)) ||
CGSizeEqualToSize([UIScreen mainScreen].bounds.size, CGSizeMake(926, 428)));
}
+ (BOOL)tui_isLandscape {
if ([UIApplication sharedApplication].statusBarOrientation == UIDeviceOrientationLandscapeRight ||
[UIApplication sharedApplication].statusBarOrientation == UIDeviceOrientationLandscapeLeft) {
return true;
}
return false;
}
+ (CGFloat)tui_statusBarHeight {
if ([UIWindow instancesRespondToSelector:@selector(safeAreaInsets)]) {
return [self tui_safeAreaInsets].top ?: 20;
}
return 20;
}
+ (NSDictionary *)tui_getInfoDictionary {
NSDictionary *infoDict = [NSBundle mainBundle].localizedInfoDictionary;
if (!infoDict || !infoDict.count) {
infoDict = [NSBundle mainBundle].infoDictionary;
}
if (!infoDict || !infoDict.count) {
NSString *path = [[NSBundle mainBundle] pathForResource:@"Info" ofType:@"plist"];
infoDict = [NSDictionary dictionaryWithContentsOfFile:path];
}
return infoDict ? infoDict : @{};
}
+ (NSString *)tui_getAppName {
NSDictionary *infoDict = [self tui_getInfoDictionary];
NSString *appName = [infoDict valueForKey:@"CFBundleDisplayName"];
if (!appName) appName = [infoDict valueForKey:@"CFBundleName"];
if (!appName) appName = [infoDict valueForKey:@"CFBundleExecutable"];
if (!appName) {
infoDict = [NSBundle mainBundle].infoDictionary;
appName = [infoDict valueForKey:@"CFBundleDisplayName"];
if (!appName) appName = [infoDict valueForKey:@"CFBundleName"];
if (!appName) appName = [infoDict valueForKey:@"CFBundleExecutable"];
}
return appName;
}
+ (BOOL)tui_isRightToLeftLayout {
return [TUIGlobalization getRTLOption];
}
+ (void)configBarButtonItem:(UIBarButtonItem *)item multiMediaNavVC:(TUIMultimediaNavController *)multiMediaNavVC {
item.tintColor = multiMediaNavVC.barItemTextColor;
NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];
textAttrs[NSForegroundColorAttributeName] = multiMediaNavVC.barItemTextColor;
textAttrs[NSFontAttributeName] = multiMediaNavVC.barItemTextFont;
[item setTitleTextAttributes:textAttrs forState:UIControlStateNormal];
NSMutableDictionary *textAttrsHighlighted = [NSMutableDictionary dictionary];
textAttrsHighlighted[NSFontAttributeName] = multiMediaNavVC.barItemTextFont;
[item setTitleTextAttributes:textAttrsHighlighted forState:UIControlStateHighlighted];
}
+ (BOOL)isICloudSyncError:(NSError *)error {
if (!error) return NO;
if ([error.domain isEqualToString:@"CKErrorDomain"] || [error.domain isEqualToString:@"CloudPhotoLibraryErrorDomain"]) {
return YES;
}
return NO;
}
@end
@interface TUIImagePickerConfig ()
@property (strong, nonatomic) NSSet *supportedLanguages;
@end
@implementation TUIImagePickerConfig
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static TUIImagePickerConfig *config = nil;
dispatch_once(&onceToken, ^{
if (config == nil) {
config = [[TUIImagePickerConfig alloc] init];
config.supportedLanguages = [NSSet setWithObjects:@"zh-Hans",@"zh-Hant",@"en", @"ar", nil];
config.preferredLanguage = nil;
}
});
NSString *identifer = [TUIGlobalization getPreferredLanguage];
[config setPreferredLanguage:identifer];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(onChangeLanguage) name:TUIChangeLanguageNotification object:nil];
return config;
}
+ (void)onChangeLanguage {
NSString *identifer = [TUIGlobalization getPreferredLanguage];
[[self sharedInstance] setPreferredLanguage:identifer];
}
- (void)setPreferredLanguage:(NSString *)preferredLanguage {
_preferredLanguage = preferredLanguage;
if (!preferredLanguage || !preferredLanguage.length) {
preferredLanguage = [NSLocale preferredLanguages].firstObject;
}
NSString *usedLanguage = @"en";
for (NSString *language in self.supportedLanguages) {
if ([preferredLanguage hasPrefix:language]) {
usedLanguage = language;
break;
}
}
_languageBundle = [NSBundle bundleWithPath:[[NSBundle tz_imagePickerBundle] pathForResource:usedLanguage ofType:@"lproj"]];
}
@end

View File

@@ -0,0 +1,16 @@
//
// TUIPhotoPickerController.h
// TUIPhotoPickerController
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <TUIMultimediaCore/TUIAssetModel.h>
@interface TUIPhotoPickerController : UIViewController
@property (nonatomic, assign) BOOL isFirstAppear;
@property (nonatomic, assign) NSInteger columnNumber;
@property (nonatomic, strong) TUIAlbumModel *albumModel;//默认选中的类别 bucket
@property (nonatomic, strong) NSMutableArray *assetModels;//该类别下所有的照片
@end

View File

@@ -0,0 +1,927 @@
//
// TUIPhotoPickerController.m
// TUIPhotoPickerController
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import "TUIPhotoPickerController.h"
#import "TUIMultimediaNavController.h"
#import "TUIPhotoPreviewController.h"
#import <TUIMultimediaCore/TUIAssetCell.h>
#import <TUIMultimediaCore/TUIAssetModel.h>
#import <TUIMultimediaCore/TUIAuthFooterTipView.h>
#import <TUICore/UIView+TUILayout.h>
#import <TUICore/UIView+TUIUtil.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import "TUIRequestOperation.h"
#import <TIMCommon/TIMDefine.h>
#import <PhotosUI/PhotosUI.h>
#import <TUIMultimediaCore/TUIImageManager.h>
#import <TUIMultimediaCore/TUIAlbumCollectionView.h>
#import <TUIMultimediaCore/TUIMultimediaCore.h>
@interface TUIPhotoPickerController ()<UICollectionViewDataSource,UICollectionViewDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate, PHPhotoLibraryChangeObserver,UITableViewDataSource,UITableViewDelegate> {
TUIAlbumModel *_albumModel;
UIView *_bottomToolBar;
UIButton *_previewButton;
UIButton *_doneButton;
UIImageView *_numberImageView;
UILabel *_numberLabel;
UIButton *_originalPhotoButton;
UILabel *_originalPhotoLabel;
BOOL _shouldScrollToBottom;
BOOL _showTakePhotoBtn;
BOOL _authorizationLimited;
CGFloat _offsetItemCount;
}
@property CGRect previousPreheatRect;
@property (nonatomic, assign) BOOL isSelectOriginalPhoto;
@property (nonatomic, strong) TUIAlbumCollectionView *collectionView;
@property (nonatomic, strong) TUIAuthFooterTipView *authFooterTipView;
@property (nonatomic, strong) UICollectionViewFlowLayout *layout;
@property (nonatomic, strong) UIImagePickerController *imagePickerVc;
@property (nonatomic, strong) CLLocation *location;
@property (nonatomic, strong) NSOperationQueue *operationQueue;
@property (nonatomic, assign) BOOL isSavingMedia;
@property (nonatomic, assign) BOOL isFetchingMedia;
@property (nonatomic, strong) UIButton *albumChangeButton;
@property (nonatomic, strong) UIImageView *albumChangeIcon;
@property (nonatomic, strong) UIView *albumBKView;
@property (nonatomic, strong) UITableView *albumTableView;
@property (nonatomic, strong) NSMutableArray *albumList;
@property (nonatomic, assign) BOOL isShowingAlbums;
@end
static CGSize AssetGridThumbnailSize;
static CGFloat itemMargin = 5;
@implementation TUIPhotoPickerController
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (UIImagePickerController *)imagePickerVc {
if (_imagePickerVc == nil) {
_imagePickerVc = [[UIImagePickerController alloc] init];
_imagePickerVc.delegate = self;
_imagePickerVc.navigationBar.barTintColor = self.navigationController.navigationBar.barTintColor;
_imagePickerVc.navigationBar.tintColor = self.navigationController.navigationBar.tintColor;
UIBarButtonItem *tzBarItem, *BarItem;
if (@available(iOS 9, *)) {
tzBarItem = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[TUIMultimediaNavController class]]];
BarItem = [UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UIImagePickerController class]]];
} else {
tzBarItem = [UIBarButtonItem appearanceWhenContainedIn:[TUIMultimediaNavController class], nil];
BarItem = [UIBarButtonItem appearanceWhenContainedIn:[UIImagePickerController class], nil];
}
NSDictionary *titleTextAttributes = [tzBarItem titleTextAttributesForState:UIControlStateNormal];
[BarItem setTitleTextAttributes:titleTextAttributes forState:UIControlStateNormal];
}
return _imagePickerVc;
}
- (void)setAlbumModel:(TUIAlbumModel *)albumModel {
if (albumModel) {
_albumModel = albumModel;
dispatch_async(dispatch_get_main_queue(), ^{
[self fetchAlbums];
[self fetchAssets];
});
}
}
- (TUIAlbumModel *)albumModel {
return _albumModel;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
multiMediaNavVC.isSelectOriginalPhoto = _isSelectOriginalPhoto;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Determine the size of the thumbnails to request from the PHCachingImageManager
CGFloat scale = 2.0;
if ([UIScreen mainScreen].bounds.size.width > 600) {
scale = 1.0;
}
CGSize cellSize = ((UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout).itemSize;
AssetGridThumbnailSize = CGSizeMake(cellSize.width * scale, cellSize.height * scale);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.isFirstAppear = NO;
}
- (BOOL)prefersStatusBarHidden {
return NO;
}
- (void)navLeftBarButtonClick{
[self.navigationController popViewControllerAnimated:YES];
}
- (void)viewDidLoad {
[super viewDidLoad];
if ([[TUIImageManager defaultManager] authorizationStatusAuthorized] || [System_Version floatValue] < 15.0) {
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
}
self.isFirstAppear = YES;
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
_isSelectOriginalPhoto = multiMediaNavVC.isSelectOriginalPhoto;
_shouldScrollToBottom = YES;
if (@available(iOS 13.0, *)) {
self.view.backgroundColor = UIColor.tertiarySystemBackgroundColor;
} else {
self.view.backgroundColor = [UIColor whiteColor];
}
UIView *albumChangeView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
albumChangeView.backgroundColor = [UIColor clearColor];
self.albumChangeButton =[UIButton buttonWithType:UIButtonTypeCustom];
[self.albumChangeButton addTarget:self action:@selector(onAlbumChangeButtonClick) forControlEvents:UIControlEventTouchUpInside];
[albumChangeView addSubview:self.albumChangeButton];
self.albumChangeIcon = [[UIImageView alloc] init];
self.albumChangeIcon.image = [UIImage tui_imageNamedFromMyBundle:@"change_album"];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onAlbumChangeButtonClick)];
[self.albumChangeIcon addGestureRecognizer:tap];
self.albumChangeIcon.userInteractionEnabled = YES;
[albumChangeView addSubview:self.albumChangeIcon];
self.navigationItem.titleView = albumChangeView;
[self refreshAlbumChangeView];
UIBarButtonItem *cancelItem = [[UIBarButtonItem alloc] initWithTitle:multiMediaNavVC.cancelBtnTitleStr style:UIBarButtonItemStylePlain target:multiMediaNavVC action:@selector(onCancelButtonClick)];
[TUICommonTools configBarButtonItem:cancelItem multiMediaNavVC:multiMediaNavVC];
self.navigationItem.leftBarButtonItem = cancelItem;
_showTakePhotoBtn = NO;
_authorizationLimited = self.albumModel.isCameraRoll && [[TUIImageManager defaultManager] isPHAuthorizationStatusLimited];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarOrientationNotification:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
[self configCollectionView];
[self configBottomToolBar];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.view.backgroundColor = [UIColor blackColor];
CGFloat top = 0;
CGFloat collectionViewHeight = 0;
CGFloat naviBarHeight = self.navigationController.navigationBar.mm_h;
CGFloat footerTipViewH = _authorizationLimited ? 80 : 0;
BOOL isStatusBarHidden = [UIApplication sharedApplication].isStatusBarHidden;
BOOL isFullScreen = self.view.mm_h == [UIScreen mainScreen].bounds.size.height;
CGFloat toolBarHeight = 50 + [TUICommonTools tui_safeAreaInsets].bottom;
if (self.navigationController.navigationBar.isTranslucent) {
top = naviBarHeight;
if (!isStatusBarHidden && isFullScreen) {
top += [TUICommonTools tui_statusBarHeight];
}
collectionViewHeight = self.view.mm_h - toolBarHeight - top;
} else {
collectionViewHeight = self.view.mm_h - toolBarHeight;
}
collectionViewHeight -= footerTipViewH;
self.collectionView.frame = CGRectMake(0, top, self.view.mm_w, collectionViewHeight);
CGFloat itemWH = (self.view.mm_w - (self.columnNumber + 1) * itemMargin) / self.columnNumber;
_layout.itemSize = CGSizeMake(itemWH, itemWH);
_layout.minimumInteritemSpacing = itemMargin;
_layout.minimumLineSpacing = itemMargin;
[self.collectionView setCollectionViewLayout:_layout];
if (_offsetItemCount > 0) {
CGFloat offsetY = _offsetItemCount * (_layout.itemSize.height + _layout.minimumLineSpacing);
[self.collectionView setContentOffset:CGPointMake(0, offsetY)];
}
CGFloat toolBarTop = 0;
if (!self.navigationController.navigationBar.isHidden) {
toolBarTop = self.view.mm_h - toolBarHeight;
} else {
CGFloat navigationHeight = naviBarHeight + [TUICommonTools tui_statusBarHeight];
toolBarTop = self.view.mm_h - toolBarHeight - navigationHeight;
}
_bottomToolBar.frame = CGRectMake(0, toolBarTop, self.view.mm_w, toolBarHeight);
if (_authFooterTipView) {
CGFloat footerTipViewY = _bottomToolBar ? toolBarTop - footerTipViewH : self.view.mm_h - footerTipViewH;
_authFooterTipView.frame = CGRectMake(0, footerTipViewY, self.view.mm_w, footerTipViewH);;
}
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
CGFloat previewWidth = [multiMediaNavVC.previewBtnTitleStr boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size.width + 2;
_previewButton.frame = CGRectMake(10, 3, previewWidth, 44);
CGFloat fullImageWidth = [multiMediaNavVC.fullImageBtnTitleStr boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13]} context:nil].size.width;
fullImageWidth += 40;
_originalPhotoButton.frame = CGRectMake(_bottomToolBar.mm_w / 2 - fullImageWidth / 2, 0, fullImageWidth, 44);
[_originalPhotoLabel sizeToFit];
_originalPhotoLabel.frame = CGRectMake(self.view.mm_w /2 -_originalPhotoLabel.mm_w /2 ,
33,
_originalPhotoLabel.mm_w,
_originalPhotoLabel.mm_h);
[_doneButton sizeToFit];
_doneButton.frame = CGRectMake(self.view.mm_w - _doneButton.mm_w - 12, 0, MAX(44, _doneButton.mm_w), 50);
_numberImageView.frame = CGRectMake(_doneButton.mm_x - 24 - 5, 13, 24, 24);
_numberLabel.frame = _numberImageView.frame;
if (isRTL()) {
for (UIView *subview in _bottomToolBar.subviews) {
[subview resetFrameToFitRTL];
}
}
[TUIImageManager defaultManager].columnNumber = [TUIImageManager defaultManager].columnNumber;
}
#pragma mark Album Logic
- (UIView *)albumBKView{
if (!_albumBKView) {
_albumBKView = [[UIView alloc] initWithFrame:CGRectMake(self.collectionView.mm_x, self.collectionView.mm_y,
self.collectionView.mm_w, self.view.mm_h - self.collectionView.mm_x)];
_albumBKView.backgroundColor = [UIColor blackColor];
_albumBKView.alpha = 0;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClickAlbumBKView)];
[_albumBKView addGestureRecognizer:tap];
[self.view insertSubview:_albumBKView belowSubview:self.albumTableView];
}
return _albumBKView;
}
- (UITableView *)albumTableView {
if (!_albumTableView) {
CGFloat tableViewHeight = self.collectionView.mm_h - 100;
_albumTableView = [[UITableView alloc] initWithFrame:CGRectMake(self.collectionView.mm_x, self.collectionView.mm_y - tableViewHeight,
self.collectionView.mm_w, tableViewHeight) style:UITableViewStylePlain];
_albumTableView.rowHeight = 54;
_albumTableView.backgroundColor = RGB(45, 45, 45);
_albumTableView.separatorColor = RGB(75, 75, 75);
_albumTableView.tableFooterView = [[UIView alloc] init];
_albumTableView.dataSource = self;
_albumTableView.delegate = self;
[_albumTableView registerClass:[TUIAlbumCell class] forCellReuseIdentifier:@"TUIAlbumCell"];
[self.view addSubview:_albumTableView];
}
return _albumTableView;
}
- (void)onAlbumChangeButtonClick {
if (self.isShowingAlbums) {
[self hideAllAlbums];
} else {
[self showAllAlbums];
}
}
- (void)onClickAlbumBKView {
[self hideAllAlbums];
}
- (void)fetchAlbums {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@weakify(self)
[[TUIMultimediaCore defaultManager] getBucketList:!self.isFirstAppear completion:^(NSArray<TUIAlbumModel *> * _Nonnull bucketList) {
@strongify(self)
dispatch_async(dispatch_get_main_queue(), ^{
self.albumList = [NSMutableArray arrayWithArray:bucketList];
});
}];
});
}
- (void)showAllAlbums {
[self refreshAlbumTableView];
[UIView animateWithDuration:0.2 animations:^{
self.albumBKView.alpha = 0.5;
self.albumTableView.frame = CGRectOffset(self.albumTableView.frame, 0, self.albumTableView.mm_h);
self.albumChangeIcon.transform = CGAffineTransformMakeRotation(M_PI);
}];
self.isShowingAlbums = YES;
}
- (void)hideAllAlbums {
[UIView animateWithDuration:0.2 animations:^{
self.albumBKView.alpha = 0;
self.albumTableView.frame = CGRectOffset(self.albumTableView.frame, 0, -self.albumTableView.mm_h);
self.albumChangeIcon.transform = CGAffineTransformMakeRotation(0);
}];
self.isShowingAlbums = NO;
}
- (void)refreshAlbumTableView {
[self updateAlbumStatus];
[self.albumTableView reloadData];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@weakify(self)
[[TUIMultimediaCore defaultManager] getBucketList:!self.isFirstAppear completion:^(NSArray<TUIAlbumModel *> * _Nonnull bucketList) {
@strongify(self)
dispatch_async(dispatch_get_main_queue(), ^{
self.albumList = [NSMutableArray arrayWithArray:bucketList];
[self updateAlbumStatus];
[self.albumTableView reloadData];
});
}];
});
}
- (void)updateAlbumStatus {
for (TUIAlbumModel *model in self.albumList) {
if ([model.name isEqualToString:self.albumModel.name]) {
model.isSelected = YES;
} else {
model.isSelected = NO;
}
}
}
- (void)refreshAlbumChangeView {
CGFloat albumChangeIconSize = 20;
[self.albumChangeButton setTitle:self.albumModel.name forState:UIControlStateNormal];
[self.albumChangeButton sizeToFit];
self.albumChangeButton.mm_y = 5;
self.albumChangeButton.mm_centerX = (self.navigationItem.titleView.mm_w - albumChangeIconSize) / 2;
self.albumChangeIcon.mm_width(20).mm_height(20).mm_left(self.albumChangeButton.mm_maxX + 4).mm__centerY(self.albumChangeButton.mm_centerY);
if (isRTL()) {
[self.albumChangeButton resetFrameToFitRTL];
[self.albumChangeIcon resetFrameToFitRTL];
}
}
#pragma mark Asset Logic
- (void)configCollectionView {
if (!self.collectionView) {
_layout = [[UICollectionViewFlowLayout alloc] init];
self.collectionView = [[TUIAlbumCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_layout];
self.collectionView.backgroundColor = RGB(25, 25, 25);
self.collectionView.alwaysBounceHorizontal = NO;
self.collectionView.contentInset = UIEdgeInsetsMake(itemMargin, itemMargin, itemMargin, itemMargin);
self.collectionView.maxSelectDescribeText = [NSBundle tui_localizedStringForKey:@"Select a maximum of %zd photos"];
[self.view addSubview:self.collectionView];
if (self.albumModel) {
[self.collectionView setBucket:self.albumModel];
[self prepareScrollCollectionViewToBottom];
}
[self.collectionView setClickListener:(id)self];
}
self.collectionView.contentSize = CGSizeMake(self.view.mm_w, (([self getAllCellCount] + self.columnNumber - 1) / self.columnNumber) * self.view.mm_w);
if (!_authFooterTipView && _authorizationLimited) {
_authFooterTipView = [[TUIAuthFooterTipView alloc] initWithFrame:CGRectMake(0, 0, self.view.mm_w, 80)];
_authFooterTipView.tipImage = [UIImage tui_imageNamedFromMyBundle:@"tip"];
_authFooterTipView.deftailImage = [UIImage tui_imageNamedFromMyBundle:@"right_arrow"];
UITapGestureRecognizer *footTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openSettingsApplication)];
[_authFooterTipView addGestureRecognizer:footTap];
[self.view addSubview:_authFooterTipView];
}
}
- (void)fetchAssets {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
CGFloat systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (self->_showTakePhotoBtn || self->_isFirstAppear || !self.albumModel.assetModels || systemVersion >= 14.0) {
@weakify(self)
[[TUIImageManager defaultManager] getAssetsFromFetchResult:self.albumModel.result completion:^(NSArray<TUIAssetModel *> *models) {
@strongify(self)
self.assetModels = [NSMutableArray arrayWithArray:models];
}];
} else {
self.assetModels = [NSMutableArray arrayWithArray:self.albumModel.assetModels];
}
});
}
#pragma mark ToolBar Logic
- (void)configBottomToolBar {
if (_bottomToolBar) return;
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
_bottomToolBar = [[UIView alloc] initWithFrame:CGRectZero];
_bottomToolBar.backgroundColor = RGBA(29, 29, 29, 0.98);
_previewButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_previewButton addTarget:self action:@selector(onPreviewButtonClick) forControlEvents:UIControlEventTouchUpInside];
_previewButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_previewButton setTitle:multiMediaNavVC.previewBtnTitleStr forState:UIControlStateNormal];
[_previewButton setTitle:multiMediaNavVC.previewBtnTitleStr forState:UIControlStateDisabled];
[_previewButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_previewButton setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled];
_previewButton.enabled = [self.collectionView getSelectedPhotoList].count;
_originalPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom];
_originalPhotoButton.imageEdgeInsets = UIEdgeInsetsMake(0, [TUICommonTools tui_isRightToLeftLayout] ? 10 : -10, 0, 0);
[_originalPhotoButton addTarget:self action:@selector(onOriginalPhotoButtonClick) forControlEvents:UIControlEventTouchUpInside];
_originalPhotoButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_originalPhotoButton setTitle:multiMediaNavVC.fullImageBtnTitleStr forState:UIControlStateNormal];
[_originalPhotoButton setTitle:multiMediaNavVC.fullImageBtnTitleStr forState:UIControlStateSelected];
[_originalPhotoButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_originalPhotoButton setImage:multiMediaNavVC.photoOriginDefImage forState:UIControlStateNormal];
[_originalPhotoButton setImage:multiMediaNavVC.photoOriginSelImage forState:UIControlStateSelected];
_originalPhotoButton.imageView.clipsToBounds = YES;
_originalPhotoButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
_originalPhotoButton.selected = _isSelectOriginalPhoto;
_originalPhotoLabel = [[UILabel alloc] init];
_originalPhotoLabel.textAlignment = NSTextAlignmentCenter;
_originalPhotoLabel.font = [UIFont systemFontOfSize:12];
_originalPhotoLabel.textColor = [UIColor grayColor];
if (_isSelectOriginalPhoto) [self getSelectedPhotoBytes];
_doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
_doneButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_doneButton addTarget:self action:@selector(onDoneButtonClick) forControlEvents:UIControlEventTouchUpInside];
[_doneButton setTitle:multiMediaNavVC.doneBtnTitleStr forState:UIControlStateNormal];
[_doneButton setTitle:multiMediaNavVC.doneBtnTitleStr forState:UIControlStateDisabled];
[_doneButton setTitleColor:multiMediaNavVC.oKButtonTitleColorNormal forState:UIControlStateNormal];
[_doneButton setTitleColor:multiMediaNavVC.oKButtonTitleColorDisabled forState:UIControlStateDisabled];
_doneButton.enabled = [self.collectionView getSelectedPhotoList].count;
_numberImageView = [[UIImageView alloc] initWithImage:multiMediaNavVC.photoNumberIconImage];
_numberImageView.hidden = [self.collectionView getSelectedPhotoList].count <= 0;
_numberImageView.clipsToBounds = YES;
_numberImageView.contentMode = UIViewContentModeScaleAspectFit;
_numberImageView.backgroundColor = [UIColor clearColor];
_numberLabel = [[UILabel alloc] init];
_numberLabel.font = [UIFont systemFontOfSize:15];
_numberLabel.adjustsFontSizeToFitWidth = YES;
_numberLabel.textColor = [UIColor whiteColor];
_numberLabel.textAlignment = NSTextAlignmentCenter;
_numberLabel.text = [NSString stringWithFormat:@"%zd",[self.collectionView getSelectedPhotoList].count];
_numberLabel.hidden = [self.collectionView getSelectedPhotoList].count <= 0;
_numberLabel.backgroundColor = [UIColor clearColor];
_numberLabel.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onDoneButtonClick)];
[_numberLabel addGestureRecognizer:tapGesture];
[_bottomToolBar addSubview:_previewButton];
[_bottomToolBar addSubview:_doneButton];
[_bottomToolBar addSubview:_numberImageView];
[_bottomToolBar addSubview:_numberLabel];
[_bottomToolBar addSubview:_originalPhotoButton];
[_bottomToolBar addSubview:_originalPhotoLabel];
[self.view addSubview:_bottomToolBar];
}
- (void)onPreviewButtonClick {
TUIPhotoPreviewController *photoPreviewVc = [[TUIPhotoPreviewController alloc] init];
NSArray<TUIAssetModel *> * selectArray = [self.collectionView getSelectedPhotoList];
if (selectArray.count > 0) {
TUIAssetModel *firstSelectedModel = selectArray.firstObject;
NSUInteger currentIndex = 0;
for (TUIAssetModel *modelItem in self.assetModels) {
if ([modelItem.asset.localIdentifier isEqualToString:firstSelectedModel.asset.localIdentifier]) {
photoPreviewVc.currentShowIndex = currentIndex;
break;
}
currentIndex++;
}
}
@weakify(self)
photoPreviewVc.addAssetBlock = ^(TUIAssetModel *model) {
model.isSelected = YES;
[self.collectionView setSelected:model selected:YES];
};
photoPreviewVc.refreshAssetBlock = ^(TUIAssetModel *model) {
[self.collectionView freshPhoto:model];
};
photoPreviewVc.delAssetBlock = ^(TUIAssetModel *model) {
NSArray *selectedModels = [NSArray arrayWithArray:[self.collectionView getSelectedPhotoList]];
for (TUIAssetModel *modelItem in selectedModels) {
if ([model.asset.localIdentifier isEqualToString:modelItem.asset.localIdentifier]) {
model.isSelected = NO;
[self.collectionView setSelected:modelItem selected:NO];
break;
}
}
};
[self pushPhotoPrevireViewController:photoPreviewVc needCheckSelectedModels:YES];
}
- (void)onOriginalPhotoButtonClick {
_originalPhotoButton.selected = !_originalPhotoButton.isSelected;
_isSelectOriginalPhoto = _originalPhotoButton.selected;
_originalPhotoLabel.hidden = !_originalPhotoButton.isSelected;
if (_isSelectOriginalPhoto) {
[self getSelectedPhotoBytes];
}
}
- (void)onDoneButtonClick {
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
[multiMediaNavVC showProgressHUD];
_doneButton.enabled = NO;
self.isFetchingMedia = YES;
NSMutableArray *pickModels = [NSMutableArray array];
__block BOOL havenotShowAlert = YES;
__block UIAlertController *alertView;
[TUIImageManager defaultManager].shouldFixOrientation = YES;
for (NSInteger i = 0; i < [self.collectionView getSelectedPhotoList].count; i++) {
TUIAssetModel *model = [self.collectionView getSelectedPhotoList][i];
TUIRequestOperation *operation = [[TUIRequestOperation alloc] initWithAsset:model.asset completed:^(UIImage * _Nonnull photo, NSDictionary * _Nonnull info) {
TUIAssetPickModel *pickModel = [[TUIAssetPickModel alloc] init];
pickModel.image = photo;
pickModel.info = info;
pickModel.asset = model.asset;
if (model.editurl) {
pickModel.editurl = model.editurl;
}
if (model.editImage) {
pickModel.editImage = model.editImage;
}
[pickModels addObject:pickModel];
if (pickModels.count != [self.collectionView getSelectedPhotoList].count) {
return;
}
if (havenotShowAlert && alertView) {
[alertView dismissViewControllerAnimated:YES completion:^{
alertView = nil;
[self didGetAllPhotos:pickModels];
}];
} else {
[self didGetAllPhotos:pickModels];
}
} progress:^(double progress, NSError * _Nonnull error, BOOL * _Nonnull stop, NSDictionary * _Nonnull info) {
if (progress < 1 && havenotShowAlert && !alertView) {
alertView = [multiMediaNavVC showAlertWithTitle:[NSBundle tui_localizedStringForKey:@"Synchronizing photos from iCloud"]];
havenotShowAlert = NO;
return;
}
if (progress >= 1) {
havenotShowAlert = YES;
}
}];
[self.operationQueue addOperation:operation];
}
}
- (void)didGetAllPhotos:(NSArray<TUIAssetPickModel *> *)pickModels {
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
[multiMediaNavVC hideProgressHUD];
_doneButton.enabled = YES;
self.isFetchingMedia = NO;
[self.navigationController dismissViewControllerAnimated:YES completion:^{
[self callDelegateMethodWithPhotos:pickModels];
}];
}
- (void)callDelegateMethodWithPhotos:(NSArray<TUIAssetPickModel *> *)pickModels {
NSMutableArray *photos = [NSMutableArray array];
NSMutableArray *assets = [NSMutableArray array];
NSMutableArray *infoArr = [NSMutableArray array];
for (TUIAssetPickModel *model in pickModels) {
[photos addObject:model.image];
[assets addObject:model.asset];
[infoArr addObject:model.info];
}
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
if (multiMediaNavVC.didFinishPickingHandle) {
multiMediaNavVC.didFinishPickingHandle(pickModels,_isSelectOriginalPhoto);
}
if (multiMediaNavVC.didFinishPickingPhotosWithInfosHandle) {
multiMediaNavVC.didFinishPickingPhotosWithInfosHandle(pickModels,_isSelectOriginalPhoto,infoArr);
}
}
#pragma mark - Notification
- (void)didChangeStatusBarOrientationNotification:(NSNotification *)noti {
_offsetItemCount = self.collectionView.contentOffset.y / (_layout.itemSize.height + _layout.minimumLineSpacing);
}
#pragma mark - UITableViewDataSource && Delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.albumList.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TUIAlbumCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TUIAlbumCell"];
TUIMultimediaNavController *imagePickerVc = (TUIMultimediaNavController *)self.navigationController;
cell.selectedCountButton.backgroundColor = imagePickerVc.iconThemeColor;
if (indexPath.row < self.albumList.count) {
[cell fillWithData:self.albumList[indexPath.row]];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row < self.albumList.count) {
self.albumModel = self.albumList[indexPath.row];
[self.collectionView setBucket:self.albumModel];
_shouldScrollToBottom = YES;
[self refreshAlbumChangeView];
[self hideAllAlbums];
[self fetchAssets];
}
}
#pragma mark - AlbumClickListener
- (void)onClick:(NSInteger)index model:(nonnull TUIAssetModel *)model{
TUIPhotoPreviewController *photoPreviewVc = [[TUIPhotoPreviewController alloc] init];
photoPreviewVc.currentShowIndex = index;
photoPreviewVc.assetModels = self.assetModels;
@weakify(self);
photoPreviewVc.addAssetBlock = ^(TUIAssetModel *model) {
model.isSelected = YES;
[self.collectionView setSelected:model selected:YES];
};
photoPreviewVc.refreshAssetBlock = ^(TUIAssetModel *model) {
[self.collectionView freshPhoto:model];
};
photoPreviewVc.delAssetBlock = ^(TUIAssetModel *model) {
NSArray *selectedModels = [NSArray arrayWithArray:[self.collectionView getSelectedPhotoList]];
for (TUIAssetModel *modelItem in selectedModels) {
if ([model.asset.localIdentifier isEqualToString:modelItem.asset.localIdentifier]) {
model.isSelected = NO;
[self.collectionView setSelected:modelItem selected:NO];
break;
}
}
};
[self pushPhotoPrevireViewController:photoPreviewVc];
}
- (void)onSelectChanged:(TUIAssetModel *)bean {
[self checkSelectedModels];
[self refreshBottomToolBarStatus];
}
#pragma mark - Private Method
- (NSInteger)getAllCellCount {
NSInteger count = self.assetModels.count;
if (_showTakePhotoBtn) {
count += 1;
}
if (_authorizationLimited) {
count += 1;
}
return count;
}
- (NSInteger)getTakePhotoCellIndex {
if (!_showTakePhotoBtn) {
return -1;
}
return [self getAllCellCount] - 1;
}
- (NSInteger)getAddMorePhotoCellIndex {
if (!_authorizationLimited) {
return -1;
}
if (_showTakePhotoBtn) {
return [self getAllCellCount] - 2;
}
return [self getAllCellCount] - 1;
}
- (void)openSettingsApplication {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}
- (void)addMorePhoto {
if (@available(iOS 14, *)) {
[[PHPhotoLibrary sharedPhotoLibrary] presentLimitedLibraryPickerFromViewController:self];
}
}
- (void)refreshBottomToolBarStatus {
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
_previewButton.enabled = [self.collectionView getSelectedPhotoList].count > 0;
_doneButton.enabled = [self.collectionView getSelectedPhotoList].count > 0;
_numberImageView.hidden = [self.collectionView getSelectedPhotoList].count <= 0;
_numberLabel.hidden = [self.collectionView getSelectedPhotoList].count <= 0;
_numberLabel.text = [NSString stringWithFormat:@"%zd",[self.collectionView getSelectedPhotoList].count];
_originalPhotoButton.selected = (_isSelectOriginalPhoto && _originalPhotoButton.enabled);
_originalPhotoLabel.hidden = (!_originalPhotoButton.isSelected);
if (_isSelectOriginalPhoto) [self getSelectedPhotoBytes];
}
- (void)pushPhotoPrevireViewController:(TUIPhotoPreviewController *)photoPreviewVc {
[self pushPhotoPrevireViewController:photoPreviewVc needCheckSelectedModels:NO];
}
- (void)pushPhotoPrevireViewController:(TUIPhotoPreviewController *)photoPreviewVc needCheckSelectedModels:(BOOL)needCheckSelectedModels {
@weakify(self)
photoPreviewVc.assetModels = self.assetModels;
photoPreviewVc.selectedModels = [NSMutableArray arrayWithArray:[self.collectionView getSelectedPhotoList]];
photoPreviewVc.isSelectOriginalPhoto = _isSelectOriginalPhoto;
[photoPreviewVc setBackButtonClickBlock:^(BOOL isSelectOriginalPhoto) {
@strongify(self)
self.isSelectOriginalPhoto = isSelectOriginalPhoto;
if (needCheckSelectedModels) {
[self checkSelectedModels];
}
[self.collectionView reloadData];
[self refreshBottomToolBarStatus];
}];
[photoPreviewVc setDoneButtonClickBlock:^(BOOL isSelectOriginalPhoto) {
@strongify(self)
self.isSelectOriginalPhoto = isSelectOriginalPhoto;
[self onDoneButtonClick];
}];
[photoPreviewVc setDoneButtonClickBlockCropMode:^(UIImage *cropedImage, id asset) {
@strongify(self)
TUIAssetPickModel *model = [[TUIAssetPickModel alloc] init];
model.image = cropedImage;
model.asset = asset;
[self didGetAllPhotos:@[model]];
}];
[self.navigationController pushViewController:photoPreviewVc animated:YES];
}
- (void)getSelectedPhotoBytes {
NSArray *selectedModels = [self.collectionView getSelectedPhotoList];
if (0 == selectedModels.count) {
self->_originalPhotoLabel.text = @"";
return;
}
#if 0
@weakify(self)
[[TUIImageManager defaultManager] getPhotosTotalBytes:selectedModels completion:^(NSString *totalBytes) {
@strongify(self)
dispatch_async(dispatch_get_main_queue(), ^{
self->_originalPhotoLabel.text = [NSString stringWithFormat:@"%@ %@",[NSBundle tui_localizedStringForKey:@"TUIAlbumSelectorTotal"],totalBytes];
[self->_originalPhotoLabel sizeToFit];
});
}];
#endif
}
- (void)prepareScrollCollectionViewToBottom {
if (_shouldScrollToBottom && self.assetModels.count > 0) {
NSInteger item = [self getAllCellCount] - 1;
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:NO];
self->_shouldScrollToBottom = NO;
self.collectionView.hidden = NO;
} else {
self.collectionView.hidden = NO;
}
}
- (void)checkSelectedModels {
NSArray *selectedModels = [self.collectionView getSelectedPhotoList];
NSMutableSet *selectedAssets = [NSMutableSet setWithCapacity:selectedModels.count];
for (TUIAssetModel *model in selectedModels) {
[selectedAssets addObject:model.asset];
}
for (TUIAssetModel *model in self.assetModels) {
model.isSelected = NO;
if ([selectedAssets containsObject:model.asset]) {
model.isSelected = YES;
}
}
}
#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissViewControllerAnimated:YES completion:nil];
NSString *type = [info objectForKey:UIImagePickerControllerMediaType];
if ([type isEqualToString:@"public.image"]) {
TUIMultimediaNavController *imagePickerVc = (TUIMultimediaNavController *)self.navigationController;
[imagePickerVc showProgressHUD];
UIImage *photo = [info objectForKey:UIImagePickerControllerOriginalImage];
NSDictionary *meta = [info objectForKey:UIImagePickerControllerMediaMetadata];
if (photo) {
self.isSavingMedia = YES;
[[TUIImageManager defaultManager] savePhotoWithImage:photo meta:meta location:self.location completion:^(PHAsset *asset, NSError *error){
self.isSavingMedia = NO;
if (!error && asset) {
[self addPHAsset:asset];
} else {
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
[multiMediaNavVC hideProgressHUD];
}
}];
self.location = nil;
}
} else if ([type isEqualToString:@"public.movie"]) {
TUIMultimediaNavController *imagePickerVc = (TUIMultimediaNavController *)self.navigationController;
[imagePickerVc showProgressHUD];
NSURL *videoUrl = [info objectForKey:UIImagePickerControllerMediaURL];
if (videoUrl) {
self.isSavingMedia = YES;
[[TUIImageManager defaultManager] saveVideoWithUrl:videoUrl location:self.location completion:^(PHAsset *asset, NSError *error) {
self.isSavingMedia = NO;
if (!error && asset) {
[self addPHAsset:asset];
} else {
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
[multiMediaNavVC hideProgressHUD];
}
}];
self.location = nil;
}
}
}
- (void)addPHAsset:(PHAsset *)asset {
TUIAssetModel *assetModel = [[TUIImageManager defaultManager] createModelWithAsset:asset];
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
[multiMediaNavVC hideProgressHUD];
[self.assetModels addObject:assetModel];
if (multiMediaNavVC.maxImagesCount < 1) {
[self.collectionView setSelected:assetModel selected:YES];
[self onDoneButtonClick];
return;
}
if ([self.collectionView getSelectedPhotoList].count < multiMediaNavVC.maxImagesCount) {
assetModel.isSelected = YES;
[self.collectionView setSelected:assetModel selected:YES];
[self refreshBottomToolBarStatus];
}
self.collectionView.hidden = YES;
[self.collectionView reloadData];
_shouldScrollToBottom = YES;
[self prepareScrollCollectionViewToBottom];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)dealloc {
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - PHPhotoLibraryChangeObserver
- (void)photoLibraryDidChange:(PHChange *)changeInstance {
if (self.isSavingMedia || self.isFetchingMedia) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
if (self.isShowingAlbums) {
[self refreshAlbumTableView];
}
PHFetchResultChangeDetails *changeDetail = [changeInstance changeDetailsForFetchResult:self.albumModel.result];
if (changeDetail == nil) return;
if ([[TUIImageManager defaultManager] isPHAuthorizationStatusLimited]) {
NSArray *changedObjects = [changeDetail changedObjects];
changeDetail = [PHFetchResultChangeDetails changeDetailsFromFetchResult:self.albumModel.result toFetchResult:changeDetail.fetchResultAfterChanges changedObjects:changedObjects];
if (changeDetail && changeDetail.removedObjects.count) {
[self handleRemovedAssets:changeDetail.removedObjects];
}
}
if (changeDetail.hasIncrementalChanges == NO) {
[self.albumModel refreshFetchResult];
[self fetchAssets];
} else {
NSInteger insertedCount = changeDetail.insertedObjects.count;
NSInteger removedCount = changeDetail.removedObjects.count;
NSInteger changedCount = changeDetail.changedObjects.count;
if (insertedCount > 0 || removedCount > 0 || changedCount > 0) {
self.albumModel.result = changeDetail.fetchResultAfterChanges;
self.albumModel.photoCount = changeDetail.fetchResultAfterChanges.count;
[self fetchAssets];
}
}
});
}
- (void)handleRemovedAssets:(NSArray<PHAsset *> *)removedObjects {
TUIMultimediaNavController *multiMediaNavVC = (TUIMultimediaNavController *)self.navigationController;
for (PHAsset *asset in removedObjects) {
Boolean isSelected = [multiMediaNavVC.selectedAssetIds containsObject:asset.localIdentifier];
if (!isSelected) continue;
NSArray *selectedModels = [NSArray arrayWithArray:[self.collectionView getSelectedPhotoList]];
for (TUIAssetModel *modelItem in selectedModels) {
if ([asset.localIdentifier isEqualToString:modelItem.asset.localIdentifier]) {
[self.collectionView setSelected:modelItem selected:NO];
}
}
[self refreshBottomToolBarStatus];
}
}
#pragma clang diagnostic pop
@end

View File

@@ -0,0 +1,74 @@
//
// TUIPhotoPreviewCell.h
// TUIPhotoPreviewCell
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <TUIMultimediaCore/TUIAssetModel.h>
#import <TUIMultimediaCore/TUIProgressView.h>
@interface TUIAssetPreviewCell : UICollectionViewCell
@property (nonatomic, strong) TUIAssetModel *model;
@property (nonatomic, copy) void (^singleTapGestureBlock)(void);
- (void)configSubviews;
- (void)photoPreviewCollectionViewDidScroll;
@end
@class TUIPhotoPreviewView;
@interface TUIPhotoPreviewCell : TUIAssetPreviewCell
@property (nonatomic, copy) void (^imageProgressUpdateBlock)(double progress);
@property (nonatomic, strong) TUIPhotoPreviewView *previewView;
@property (nonatomic, assign) BOOL allowCrop;
@property (nonatomic, assign) CGRect cropRect;
@property (nonatomic, assign) BOOL scaleAspectFillCrop;
- (void)recoverSubviews;
@end
@interface TUIPhotoPreviewView : UIView
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *imageContainerView;
@property (nonatomic, strong) TUIProgressView *progressView;
@property (nonatomic, strong) UIImageView *iCloudErrorIcon;
@property (nonatomic, strong) UILabel *iCloudErrorLabel;
@property (nonatomic, copy) void (^iCloudSyncFailedHandle)(id asset, BOOL isSyncFailed);
@property (nonatomic, assign) CGRect cropRect;
@property (nonatomic, assign) BOOL scaleAspectFillCrop;
@property (nonatomic, strong) TUIAssetModel *model;
@property (nonatomic, strong) id asset;
@property (nonatomic, copy) void (^singleTapGestureBlock)(void);
@property (nonatomic, copy) void (^imageProgressUpdateBlock)(double progress);
@property (nonatomic, assign) int32_t imageRequestID;
- (void)recoverSubviews;
@end
@class AVPlayer, AVPlayerLayer;
@interface TUIVideoPreviewCell : TUIAssetPreviewCell
@property (strong, nonatomic) AVPlayer *player;
@property (strong, nonatomic) AVPlayerLayer *playerLayer;
@property (strong, nonatomic) UIButton *playButton;
@property (strong, nonatomic) UIImage *cover;
@property (nonatomic, strong) NSURL *videoURL;
@property (nonatomic, strong) UIImageView *iCloudErrorIcon;
@property (nonatomic, strong) UILabel *iCloudErrorLabel;
@property (nonatomic, copy) void (^iCloudSyncFailedHandle)(id asset, BOOL isSyncFailed);
- (void)pausePlayerAndShowNaviBar;
@end
@interface TUIGifPreviewCell : TUIAssetPreviewCell
@property (strong, nonatomic) TUIPhotoPreviewView *previewView;
@end

View File

@@ -0,0 +1,562 @@
//
// TUIPhotoPreviewCell.m
// TUIPhotoPreviewCell
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import "TUIPhotoPreviewCell.h"
#import "TUIMultimediaNavController.h"
#import <MediaPlayer/MediaPlayer.h>
#import <TUICore/UIView+TUILayout.h>
#import <TIMCommon/TIMDefine.h>
#import <TUIMultimediaCore/TUIAssetModel.h>
#import <TUIMultimediaCore/TUIImageManager.h>
#import <TUIMultimediaCore/TUIProgressView.h>
@implementation TUIAssetPreviewCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor blackColor];
[self configSubviews];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(photoPreviewCollectionViewDidScroll) name:@"photoPreviewCollectionViewDidScroll" object:nil];
}
return self;
}
- (void)configSubviews {
// Subclass override
}
#pragma mark - Notification
- (void)photoPreviewCollectionViewDidScroll {
// Subclass override
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end
@implementation TUIPhotoPreviewCell
- (void)configSubviews {
self.previewView = [[TUIPhotoPreviewView alloc] initWithFrame:CGRectZero];
@weakify(self)
[self.previewView setSingleTapGestureBlock:^{
@strongify(self)
if (self.singleTapGestureBlock) {
self.singleTapGestureBlock();
}
}];
[self.previewView setImageProgressUpdateBlock:^(double progress) {
@strongify(self)
if (self.imageProgressUpdateBlock) {
self.imageProgressUpdateBlock(progress);
}
}];
[self.contentView addSubview:self.previewView];
}
- (void)setModel:(TUIAssetModel *)model {
[super setModel:model];
self.previewView.model = model;
}
- (void)recoverSubviews {
[self.previewView recoverSubviews];
}
- (void)setScaleAspectFillCrop:(BOOL)scaleAspectFillCrop {
_scaleAspectFillCrop = scaleAspectFillCrop;
self.previewView.scaleAspectFillCrop = scaleAspectFillCrop;
}
- (void)setCropRect:(CGRect)cropRect {
_cropRect = cropRect;
self.previewView.cropRect = cropRect;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.previewView.frame = self.bounds;
}
@end
@interface TUIPhotoPreviewView ()<UIScrollViewDelegate>
@property (assign, nonatomic) BOOL isRequestingGIF;
@end
@implementation TUIPhotoPreviewView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.bouncesZoom = YES;
self.scrollView.maximumZoomScale = 4;
self.scrollView.minimumZoomScale = 1.0;
self.scrollView.multipleTouchEnabled = YES;
self.scrollView.delegate = self;
self.scrollView.scrollsToTop = NO;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = YES;
self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.scrollView.delaysContentTouches = NO;
self.scrollView.canCancelContentTouches = YES;
self.scrollView.alwaysBounceVertical = NO;
if (@available(iOS 11, *)) {
self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self addSubview:self.scrollView];
self.imageContainerView = [[UIView alloc] init];
self.imageContainerView.clipsToBounds = YES;
self.imageContainerView.contentMode = UIViewContentModeScaleAspectFill;
[self.scrollView addSubview:self.imageContainerView];
self.imageView = [[UIImageView alloc] init];
self.imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500];
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
self.imageView.clipsToBounds = YES;
[self.imageContainerView addSubview:self.imageView];
self.iCloudErrorIcon = [[UIImageView alloc] init];
self.iCloudErrorIcon.image = [UIImage tui_imageNamedFromMyBundle:@"iCloudError"];
self.iCloudErrorIcon.hidden = YES;
[self addSubview:self.iCloudErrorIcon];
self.iCloudErrorLabel = [[UILabel alloc] init];
self.iCloudErrorLabel.font = [UIFont systemFontOfSize:10];
self.iCloudErrorLabel.textColor = [UIColor whiteColor];
self.iCloudErrorLabel.text = [NSBundle tui_localizedStringForKey:@"iCloud sync failed"];
self.iCloudErrorLabel.hidden = YES;
[self addSubview:self.iCloudErrorLabel];
UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
[self addGestureRecognizer:tap1];
UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
tap2.numberOfTapsRequired = 2;
[tap1 requireGestureRecognizerToFail:tap2];
[self addGestureRecognizer:tap2];
self.progressView = [[TUIProgressView alloc] init];
self.progressView.hidden = YES;
[self addSubview:self.progressView];
}
return self;
}
- (void)setModel:(TUIAssetModel *)model {
_model = model;
self.isRequestingGIF = NO;
[self.scrollView setZoomScale:1.0 animated:NO];
if (TUIAssetMediaTypePhotoGif == model.type) {
// Show thumbnails first
[[TUIImageManager defaultManager] getPhotoWithAsset:model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
if (photo) {
self.imageView.image = photo;
}
[self resizeSubviews];
if (self.isRequestingGIF) {
return;
}
// Show gif
self.isRequestingGIF = YES;
[[TUIImageManager defaultManager] getOriginalPhotoDataWithAsset:model.asset progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
progress = progress > 0.02 ? progress : 0.02;
dispatch_async(dispatch_get_main_queue(), ^{
BOOL iCloudSyncFailed = [TUICommonTools isICloudSyncError:error];
self.iCloudErrorLabel.hidden = !iCloudSyncFailed;
self.iCloudErrorIcon.hidden = !iCloudSyncFailed;
if (self.iCloudSyncFailedHandle) {
self.iCloudSyncFailedHandle(model.asset, iCloudSyncFailed);
}
self.progressView.progress = progress;
if (progress >= 1) {
self.progressView.hidden = YES;
} else {
self.progressView.hidden = NO;
}
});
#ifdef DEBUG
NSLog(@"[TUIMultimediaNavController] getOriginalPhotoDataWithAsset:%f error:%@", progress, error);
#endif
} completion:^(NSData *data, NSDictionary *info, BOOL isDegraded) {
if (!isDegraded) {
self.isRequestingGIF = NO;
self.progressView.hidden = YES;
self.imageView.image = [UIImage tui_animatedGIFWithData:data];
[self resizeSubviews];
}
}];
} progressHandler:nil networkAccessAllowed:NO];
} else {
if (model.type == TUIAssetMediaTypePhoto && model.editImage != nil) {
self.imageView.image = model.editImage;
[self resizeSubviews];
self.progressView.hidden = YES;
if (self.imageProgressUpdateBlock) {
self.imageProgressUpdateBlock(1);
}
} else {
self.asset = model.asset;
}
}
}
- (void)setAsset:(PHAsset *)asset {
if (_asset && self.imageRequestID) {
[[PHImageManager defaultManager] cancelImageRequest:self.imageRequestID];
}
_asset = asset;
@weakify(self)
self.imageRequestID = [[TUIImageManager defaultManager] getPhotoWithAsset:asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
@strongify(self)
BOOL iCloudSyncFailed = !photo && [TUICommonTools isICloudSyncError:info[PHImageErrorKey]];
self.iCloudErrorLabel.hidden = !iCloudSyncFailed;
self.iCloudErrorIcon.hidden = !iCloudSyncFailed;
if (self.iCloudSyncFailedHandle) {
self.iCloudSyncFailedHandle(asset, iCloudSyncFailed);
}
if (![asset isEqual:self->_asset]) return;
if (photo) {
self.imageView.image = photo;
}
[self resizeSubviews];
self.progressView.hidden = YES;
if (self.imageProgressUpdateBlock) {
self.imageProgressUpdateBlock(1);
}
if (!isDegraded) {
self.imageRequestID = 0;
}
} progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
@strongify(self)
if (![asset isEqual:self->_asset]) return;
self.progressView.hidden = NO;
[self bringSubviewToFront:self.progressView];
progress = progress > 0.02 ? progress : 0.02;
self.progressView.progress = progress;
if (self.imageProgressUpdateBlock && progress < 1) {
self.imageProgressUpdateBlock(progress);
}
if (progress >= 1) {
self.progressView.hidden = YES;
self.imageRequestID = 0;
}
} networkAccessAllowed:YES];
[self configMaximumZoomScale];
}
- (void)recoverSubviews {
[self.scrollView setZoomScale:self.scrollView.minimumZoomScale animated:NO];
[self resizeSubviews];
}
- (void)resizeSubviews {
self.imageContainerView.mm_origin = CGPointZero;
self.imageContainerView.mm_w = self.scrollView.mm_w;
UIImage *image = self.imageView.image;
if (image.size.height / image.size.width > self.mm_h / self.scrollView.mm_w) {
CGFloat width = image.size.width / image.size.height * self.scrollView.mm_h;
if (width < 1 || isnan(width)) width = self.mm_w;
width = floor(width);
self.imageContainerView.mm_w = width;
self.imageContainerView.mm_h = self.mm_h;
self.imageContainerView.mm_centerX = self.scrollView.mm_w / 2;
} else {
CGFloat height = image.size.height / image.size.width * self.scrollView.mm_w;
if (height < 1 || isnan(height)) height = self.mm_h;
height = floor(height);
self.imageContainerView.mm_h = height;
self.imageContainerView.mm_centerY = self.mm_h / 2;
}
if (self.imageContainerView.mm_h > self.mm_h && self.imageContainerView.mm_h - self.mm_h <= 1) {
self.imageContainerView.mm_h = self.mm_h;
}
CGFloat contentSizeH = MAX(self.imageContainerView.mm_h, self.mm_h);
self.scrollView.contentSize = CGSizeMake(self.scrollView.mm_w, contentSizeH);
[self.scrollView scrollRectToVisible:self.bounds animated:NO];
self.scrollView.alwaysBounceVertical = self.imageContainerView.mm_h <= self.mm_h ? NO : YES;
self.imageView.frame = self.imageContainerView.bounds;
[self refreshScrollViewContentSize];
}
- (void)configMaximumZoomScale {
self.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)refreshScrollViewContentSize {
// to do
}
- (void)layoutSubviews {
[super layoutSubviews];
self.scrollView.frame = CGRectMake(10, 0, self.mm_w - 20, self.mm_h);
static CGFloat progressWH = 40;
CGFloat progressX = (self.mm_w - progressWH) / 2;
CGFloat progressY = (self.mm_h - progressWH) / 2;
self.progressView.frame = CGRectMake(progressX, progressY, progressWH, progressWH);
[self recoverSubviews];
self.iCloudErrorIcon.frame = CGRectMake(20, [TUICommonTools tui_statusBarHeight] + 44 + 10, 28, 28);
self.iCloudErrorLabel.frame = CGRectMake(53, [TUICommonTools tui_statusBarHeight] + 44 + 10, self.mm_w - 63, 28);
}
#pragma mark - UITapGestureRecognizer Event
- (void)doubleTap:(UITapGestureRecognizer *)tap {
if (self.scrollView.zoomScale > self.scrollView.minimumZoomScale) {
self.scrollView.contentInset = UIEdgeInsetsZero;
[self.scrollView setZoomScale:self.scrollView.minimumZoomScale animated:YES];
} else {
CGPoint touchPoint = [tap locationInView:self.imageView];
CGFloat newZoomScale = MIN(self.scrollView.maximumZoomScale, 2.5);
CGFloat xsize = self.frame.size.width / newZoomScale;
CGFloat ysize = self.frame.size.height / newZoomScale;
[self.scrollView zoomToRect:CGRectMake(touchPoint.x - xsize/2, touchPoint.y - ysize/2, xsize, ysize) animated:YES];
}
}
- (void)singleTap:(UITapGestureRecognizer *)tap {
if (self.singleTapGestureBlock) {
self.singleTapGestureBlock();
}
}
#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.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 {
[self refreshScrollViewContentSize];
}
#pragma mark - Private
- (void)refreshImageContainerViewCenter {
CGFloat offsetX = (self.scrollView.mm_w > self.scrollView.contentSize.width) ? ((self.scrollView.mm_w - self.scrollView.contentSize.width) * 0.5) : 0.0;
CGFloat offsetY = (self.scrollView.mm_h > self.scrollView.contentSize.height) ? ((self.scrollView.mm_h - self.scrollView.contentSize.height) * 0.5) : 0.0;
self.imageContainerView.center = CGPointMake(self.scrollView.contentSize.width * 0.5 + offsetX, self.scrollView.contentSize.height * 0.5 + offsetY);
}
@end
@implementation TUIVideoPreviewCell
- (void)configSubviews {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil];
self.iCloudErrorIcon = [[UIImageView alloc] init];
self.iCloudErrorIcon.image = [UIImage tui_imageNamedFromMyBundle:@"iCloudError"];
self.iCloudErrorIcon.hidden = YES;
self.iCloudErrorLabel = [[UILabel alloc] init];
self.iCloudErrorLabel.font = [UIFont systemFontOfSize:10];
self.iCloudErrorLabel.textColor = [UIColor whiteColor];
self.iCloudErrorLabel.text = [NSBundle tui_localizedStringForKey:@"iCloud sync failed"];
self.iCloudErrorLabel.hidden = YES;
}
- (void)configPlayButton {
if (self.playButton) {
[self.playButton removeFromSuperview];
}
self.playButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.playButton setImage:[UIImage tui_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
[self.playButton setImage:[UIImage tui_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted];
[self.playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside];
self.playButton.frame = CGRectMake(0, 64, self.mm_w, self.mm_h - 64 - 44);
[self.contentView addSubview:self.playButton];
[self.contentView addSubview:self.iCloudErrorIcon];
[self.contentView addSubview:self.iCloudErrorLabel];
}
- (void)setModel:(TUIAssetModel *)model {
[super setModel:model];
[self configMoviePlayer];
}
- (void)setVideoURL:(NSURL *)videoURL {
_videoURL = videoURL;
[self configMoviePlayer];
}
- (void)configMoviePlayer {
if (self.player) {
[self.playerLayer removeFromSuperlayer];
self.playerLayer = nil;
[self.player pause];
self.player = nil;
}
//,使url Image
if (self.model.editImage && self.model.type == TUIAssetMediaTypePhoto) {
self.cover = self.model.editImage;
} else if (self.model.editurl) {
if (self.model.editImage) {
self.cover = self.model.editImage;
}
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:self.model.editurl];
[self configPlayerWithItem:playerItem];
} else {
if (self.model && self.model.asset) {
[[TUIImageManager defaultManager] getPhotoWithAsset:self.model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
BOOL iCloudSyncFailed = !photo && [TUICommonTools isICloudSyncError:info[PHImageErrorKey]];
self.iCloudErrorLabel.hidden = !iCloudSyncFailed;
self.iCloudErrorIcon.hidden = !iCloudSyncFailed;
if (self.iCloudSyncFailedHandle) {
self.iCloudSyncFailedHandle(self.model.asset, iCloudSyncFailed);
}
if (photo) {
self.cover = photo;
}
}];
[[TUIImageManager defaultManager] getVideoWithAsset:self.model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) {
dispatch_async(dispatch_get_main_queue(), ^{
BOOL iCloudSyncFailed = !playerItem && [TUICommonTools isICloudSyncError:info[PHImageErrorKey]];
self.iCloudErrorLabel.hidden = !iCloudSyncFailed;
self.iCloudErrorIcon.hidden = !iCloudSyncFailed;
if (self.iCloudSyncFailedHandle) {
self.iCloudSyncFailedHandle(self.model.asset, iCloudSyncFailed);
}
[self configPlayerWithItem:playerItem];
});
}];
} else {
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:self.videoURL];
[self configPlayerWithItem:playerItem];
}
}
}
- (void)configPlayerWithItem:(AVPlayerItem *)playerItem {
self.player = [AVPlayer playerWithPlayerItem:playerItem];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.backgroundColor = [UIColor blackColor].CGColor;
self.playerLayer.frame = self.bounds;
[self.contentView.layer addSublayer:self.playerLayer];
[self configPlayButton];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
}
- (void)layoutSubviews {
[super layoutSubviews];
self.playerLayer.frame = self.bounds;
self.playButton.frame = CGRectMake(0, 64, self.mm_w, self.mm_h - 64 - 44);
self.iCloudErrorIcon.frame = CGRectMake(20, [TUICommonTools tui_statusBarHeight] + 44 + 10, 28, 28);
self.iCloudErrorLabel.frame = CGRectMake(53, [TUICommonTools tui_statusBarHeight] + 44 + 10, self.mm_w - 63, 28);
}
- (void)photoPreviewCollectionViewDidScroll {
if (self.player && self.player.rate != 0.0) {
[self pausePlayerAndShowNaviBar];
}
}
#pragma mark - Notification
- (void)appWillResignActiveNotification {
if (self.player && self.player.rate != 0.0) {
[self pausePlayerAndShowNaviBar];
}
}
#pragma mark - Click Event
- (void)playButtonClick {
CMTime currentTime = self.player.currentItem.currentTime;
CMTime durationTime = self.player.currentItem.duration;
if (self.player.rate == 0.0f) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_VIDEO_PLAY_NOTIFICATION" object:self.player];
if (currentTime.value == durationTime.value) [self.player.currentItem seekToTime:CMTimeMake(0, 1)];
[self.player play];
[self.playButton setImage:nil forState:UIControlStateNormal];
[UIApplication sharedApplication].statusBarHidden = YES;
if (self.singleTapGestureBlock) {
self.singleTapGestureBlock();
}
} else {
[self pausePlayerAndShowNaviBar];
}
}
- (void)pausePlayerAndShowNaviBar {
[self.player pause];
[self.playButton setImage:[UIImage tui_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
if (self.singleTapGestureBlock) {
self.singleTapGestureBlock();
}
}
@end
@implementation TUIGifPreviewCell
- (void)configSubviews {
[self configPreviewView];
}
- (void)configPreviewView {
_previewView = [[TUIPhotoPreviewView alloc] initWithFrame:CGRectZero];
@weakify(self)
[_previewView setSingleTapGestureBlock:^{
@strongify(self)
[self signleTapAction];
}];
[self.contentView addSubview:_previewView];
}
- (void)setModel:(TUIAssetModel *)model {
[super setModel:model];
_previewView.model = self.model;
}
- (void)layoutSubviews {
[super layoutSubviews];
_previewView.frame = self.bounds;
}
#pragma mark - Click Event
- (void)signleTapAction {
if (self.singleTapGestureBlock) {
self.singleTapGestureBlock();
}
}
@end

View File

@@ -0,0 +1,28 @@
//
// TUIPhotoPreviewController.h
// TUIPhotoPreviewController
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <TUIMultimediaCore/TUIAssetModel.h>
@interface TUIPhotoPreviewController : UIViewController
@property (nonatomic, strong) NSMutableArray *assetModels; ///< All photo models
@property (nonatomic, strong) NSMutableArray *selectedModels; ///< Select photo models
@property (nonatomic, strong) NSMutableArray *photos; ///< All photos
@property (nonatomic, assign) NSInteger currentShowIndex; ///< Index of the photo user click
@property (nonatomic, assign) BOOL isSelectOriginalPhoto; ///< If YES,return original photo
@property (nonatomic, assign) BOOL isCropImage;
/// Return the new selected photos
@property (nonatomic, copy) void (^backButtonClickBlock)(BOOL isSelectOriginalPhoto);
@property (nonatomic, copy) void (^doneButtonClickBlock)(BOOL isSelectOriginalPhoto);
@property (nonatomic, copy) void (^doneButtonClickBlockCropMode)(UIImage *cropedImage,id asset);
@property (nonatomic, copy) void (^addAssetBlock)(TUIAssetModel * model);
@property (nonatomic, copy) void (^delAssetBlock)(TUIAssetModel * model);
@property (nonatomic, copy) void (^refreshAssetBlock)(TUIAssetModel * model);
@end

View File

@@ -0,0 +1,747 @@
//
// TUIPhotoPreviewController.m
// TUIPhotoPreviewController
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import "TUIPhotoPreviewController.h"
#import "TUIPhotoPreviewCell.h"
#import "TUIMultimediaProcessor.h"
#import "TUIMultimediaNavController.h"
#import <TUICore/UIView+TUILayout.h>
#import <TUICore/UIView+TUIUtil.h>
#import <TUICore/TUICore.h>
#import <TIMCommon/TIMDefine.h>
#import <TUIMultimediaCore/TUISelectedPhotosView.h>
#import <TUIMultimediaCore/TUIAssetModel.h>
#import <TUIMultimediaCore/TUIImageManager.h>
@interface TUIPhotoPreviewController ()<UICollectionViewDataSource,UICollectionViewDelegate,UIScrollViewDelegate> {
UICollectionView *_collectionView;
UICollectionViewFlowLayout *_layout;
UIView *_naviBar;
UIButton *_backButton;
UIButton *_selectButton;
UILabel *_indexLabel;
UIView *_toolBar;
UIButton *_doneButton;
UIImageView *_numberImageView;
UILabel *_numberLabel;
TUISelectedPhotosView *_selectedScrollView;
UIButton *_editPhotoButton;
UIButton *_originalPhotoButton;
UILabel *_originalPhotoLabel;
CGFloat _offsetItemCount;
BOOL _didSetIsSelectOriginalPhoto;
}
@property (nonatomic, assign) BOOL isHideNaviBar;
@property (nonatomic, assign) double progress;
@property (strong, nonatomic) UIAlertController *alertView;
@property (nonatomic, strong) UIView *iCloudErrorView;
@property (nonatomic, strong) TUIMultimediaNavController *multiMediaNavVC;
@end
@implementation TUIPhotoPreviewController
- (void)setIsSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto {
_isSelectOriginalPhoto = isSelectOriginalPhoto;
_didSetIsSelectOriginalPhoto = YES;
}
- (void)setPhotos:(NSMutableArray *)photos {
_photos = photos;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:YES];
[UIApplication sharedApplication].statusBarHidden = YES;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.multiMediaNavVC.needShowStatusBar) {
[UIApplication sharedApplication].statusBarHidden = NO;
}
[self.navigationController setNavigationBarHidden:NO animated:YES];
[TUIImageManager defaultManager].shouldFixOrientation = NO;
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.clipsToBounds = YES;
[TUIImageManager defaultManager].shouldFixOrientation = YES;
if (!_didSetIsSelectOriginalPhoto) {
_isSelectOriginalPhoto = self.multiMediaNavVC.isSelectOriginalPhoto;
}
[self configCollectionView];
[self configCustomNaviBar];
[self configSelectedScrollView];
[self configBottomToolBar];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarOrientationNotification:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[_collectionView setContentOffset:CGPointMake((self.view.mm_w + 20) * self.currentShowIndex, 0) animated:NO];
[self refreshNaviBarAndBottomBarState];
[self updateSelectedScrollViewWithModels:self.selectedModels];
[self onScrollToItemAtIndex:self.currentShowIndex];
}
- (TUIMultimediaNavController *)multiMediaNavVC {
return (TUIMultimediaNavController *)self.navigationController;
}
- (void)configCustomNaviBar {
_naviBar = [[UIView alloc] initWithFrame:CGRectZero];
_naviBar.backgroundColor = RGBA(29, 29, 29, 0.98);
_backButton = [[UIButton alloc] initWithFrame:CGRectZero];
[_backButton setImage:[[UIImage tui_imageNamedFromMyBundle:@"navi_back"]
rtl_imageFlippedForRightToLeftLayoutDirection]
forState:UIControlStateNormal];
[_backButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_backButton addTarget:self action:@selector(onBackButtonClick) forControlEvents:UIControlEventTouchUpInside];
_selectButton = [[UIButton alloc] initWithFrame:CGRectZero];
[_selectButton setImage:self.multiMediaNavVC.photoDefImage forState:UIControlStateNormal];
[_selectButton setImage:self.multiMediaNavVC.photoSelImage forState:UIControlStateSelected];
_selectButton.imageView.clipsToBounds = YES;
_selectButton.imageEdgeInsets = UIEdgeInsetsMake(10, 0, 10, 0);
_selectButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
[_selectButton addTarget:self action:@selector(onSelectButtonClick:) forControlEvents:UIControlEventTouchUpInside];
_selectButton.hidden = YES;
_indexLabel = [[UILabel alloc] init];
_indexLabel.adjustsFontSizeToFitWidth = YES;
_indexLabel.font = [UIFont systemFontOfSize:14];
_indexLabel.textColor = [UIColor whiteColor];
_indexLabel.textAlignment = NSTextAlignmentCenter;
[_naviBar addSubview:_selectButton];
[_naviBar addSubview:_indexLabel];
[_naviBar addSubview:_backButton];
[self.view addSubview:_naviBar];
}
- (void)configSelectedScrollView {
_selectedScrollView = [[TUISelectedPhotosView alloc] init];
[_selectedScrollView setPhotoList:self.selectedModels];
[_selectedScrollView setClickListener:self];
_selectedScrollView.backgroundColor = RGBA(29, 29, 29, 0.98);
_selectedScrollView.bouncesZoom = YES;
_selectedScrollView.maximumZoomScale = 4;
_selectedScrollView.minimumZoomScale = 1.0;
_selectedScrollView.multipleTouchEnabled = YES;
// _selectedScrollView.delegate = self;
_selectedScrollView.scrollsToTop = NO;
_selectedScrollView.showsHorizontalScrollIndicator = NO;
_selectedScrollView.showsVerticalScrollIndicator = YES;
_selectedScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_selectedScrollView.delaysContentTouches = NO;
_selectedScrollView.canCancelContentTouches = YES;
_selectedScrollView.alwaysBounceVertical = NO;
_selectedScrollView.pagingEnabled = NO;
_selectedScrollView.bounces = NO;
if (@available(iOS 11, *)) {
_selectedScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self.view addSubview:_selectedScrollView];
}
- (void)configBottomToolBar {
_toolBar = [[UIView alloc] initWithFrame:CGRectZero];
_toolBar.backgroundColor = RGBA(29, 29, 29, 0.98);
_editPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom];
_editPhotoButton.backgroundColor = [UIColor clearColor];
[_editPhotoButton addTarget:self action:@selector(onEditPhotoButtonClick) forControlEvents:UIControlEventTouchUpInside];
_editPhotoButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_editPhotoButton setTitle:self.multiMediaNavVC.editImageBtnTitleStr forState:UIControlStateNormal];
[_editPhotoButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
_originalPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom];
_originalPhotoButton.imageEdgeInsets = UIEdgeInsetsMake(0, [TUICommonTools tui_isRightToLeftLayout] ? 10 : -10, 0, 0);
_originalPhotoButton.backgroundColor = [UIColor clearColor];
[_originalPhotoButton addTarget:self action:@selector(onOriginalPhotoButtonClick) forControlEvents:UIControlEventTouchUpInside];
_originalPhotoButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_originalPhotoButton setTitle:self.multiMediaNavVC.fullImageBtnTitleStr forState:UIControlStateNormal];
[_originalPhotoButton setTitle:self.multiMediaNavVC.fullImageBtnTitleStr forState:UIControlStateSelected];
[_originalPhotoButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
[_originalPhotoButton setTitleColor:[UIColor whiteColor] forState:UIControlStateSelected];
[_originalPhotoButton setImage:self.multiMediaNavVC.photoPreviewOriginDefImage forState:UIControlStateNormal];
[_originalPhotoButton setImage:self.multiMediaNavVC.photoOriginSelImage forState:UIControlStateSelected];
_originalPhotoLabel = [[UILabel alloc] init];
_originalPhotoLabel.textAlignment = NSTextAlignmentCenter;
_originalPhotoLabel.font = [UIFont systemFontOfSize:12];
_originalPhotoLabel.textColor = [UIColor grayColor];
_originalPhotoLabel.backgroundColor = [UIColor clearColor];
if (_isSelectOriginalPhoto) [self showTotalPhotoBytes];
_doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
_doneButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_doneButton addTarget:self action:@selector(onDoneButtonClick) forControlEvents:UIControlEventTouchUpInside];
[_doneButton setTitle:self.multiMediaNavVC.doneBtnTitleStr forState:UIControlStateNormal];
[_doneButton setTitleColor:self.multiMediaNavVC.oKButtonTitleColorNormal forState:UIControlStateNormal];
_numberImageView = [[UIImageView alloc] initWithImage:self.multiMediaNavVC.photoNumberIconImage];
_numberImageView.backgroundColor = [UIColor clearColor];
_numberImageView.clipsToBounds = YES;
_numberImageView.contentMode = UIViewContentModeScaleAspectFit;
_numberImageView.hidden = self.selectedModels.count <= 0;
_numberLabel = [[UILabel alloc] init];
_numberLabel.font = [UIFont systemFontOfSize:15];
_numberLabel.adjustsFontSizeToFitWidth = YES;
_numberLabel.textColor = [UIColor whiteColor];
_numberLabel.textAlignment = NSTextAlignmentCenter;
_numberLabel.text = [NSString stringWithFormat:@"%zd",self.selectedModels.count];
_numberLabel.hidden = self.selectedModels.count <= 0;
_numberLabel.backgroundColor = [UIColor clearColor];
_numberLabel.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onDoneButtonClick)];
[_numberLabel addGestureRecognizer:tapGesture];
[_toolBar addSubview:_doneButton];
[_toolBar addSubview:_editPhotoButton];
[_toolBar addSubview:_originalPhotoButton];
[_toolBar addSubview:_originalPhotoLabel];
[_toolBar addSubview:_numberImageView];
[_toolBar addSubview:_numberLabel];
[self.view addSubview:_toolBar];
}
- (void)configCollectionView {
_layout = [[UICollectionViewFlowLayout alloc] init];
_layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_layout];
_collectionView.backgroundColor = [UIColor blackColor];
_collectionView.dataSource = self;
_collectionView.delegate = self;
_collectionView.pagingEnabled = YES;
_collectionView.scrollsToTop = NO;
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.contentOffset = CGPointMake(0, 0);
_collectionView.contentSize = CGSizeMake(self.assetModels.count * (self.view.mm_w + 20), 0);
if (@available(iOS 11, *)) {
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
[self.view addSubview:_collectionView];
[_collectionView registerClass:[TUIPhotoPreviewCell class] forCellWithReuseIdentifier:@"TUIPhotoPreviewCell"];
[_collectionView registerClass:[TUIPhotoPreviewCell class] forCellWithReuseIdentifier:@"TUIPhotoPreviewCellGIF"];
[_collectionView registerClass:[TUIVideoPreviewCell class] forCellWithReuseIdentifier:@"TUIVideoPreviewCell"];
[_collectionView registerClass:[TUIGifPreviewCell class] forCellWithReuseIdentifier:@"TUIGifPreviewCell"];
}
#pragma mark - Layout
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
BOOL isFullScreen = (self.view.mm_h == Screen_Height);
CGFloat statusBarHeight = isFullScreen ? [TUICommonTools tui_statusBarHeight] : 0;
CGFloat statusBarHeightInterval = isFullScreen ? (statusBarHeight - 20) : 0;
CGFloat naviBarHeight = statusBarHeight + self.multiMediaNavVC.navigationBar.mm_h;
_naviBar.frame = CGRectMake(0, 0, self.view.mm_w, naviBarHeight);
_backButton.frame = CGRectMake(10, 10 + statusBarHeightInterval, 44, 44);
_selectButton.frame = CGRectMake(self.view.mm_w - 56, 10 + statusBarHeightInterval, 44, 44);
_indexLabel.frame = _selectButton.frame;
_layout.itemSize = CGSizeMake(self.view.mm_w + 20, self.view.mm_h);
_layout.minimumInteritemSpacing = 0;
_layout.minimumLineSpacing = 0;
_collectionView.frame = CGRectMake(-10, 0, self.view.mm_w + 20, self.view.mm_h);
[_collectionView setCollectionViewLayout:_layout];
if (_offsetItemCount > 0) {
CGFloat offsetX = _offsetItemCount * _layout.itemSize.width;
[_collectionView setContentOffset:CGPointMake(offsetX, 0)];
}
CGFloat toolBarHeight = 44 + [TUICommonTools tui_safeAreaInsets].bottom + 2;
CGFloat toolBarTop = self.view.mm_h - toolBarHeight;
_toolBar.frame = CGRectMake(0, toolBarTop, self.view.mm_w, toolBarHeight);
_editPhotoButton.frame = CGRectMake(0, 0, 56, 44);
CGFloat fullImageWidth = [self.multiMediaNavVC.fullImageBtnTitleStr boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13]} context:nil].size.width;
fullImageWidth += 40;
_originalPhotoButton.frame = CGRectMake(_toolBar.mm_w / 2 - fullImageWidth / 2, 0, fullImageWidth, 44);
[_originalPhotoLabel sizeToFit];
_originalPhotoLabel.frame = CGRectMake(_toolBar.mm_w /2 -_originalPhotoLabel.mm_w /2 ,
33,
_originalPhotoLabel.mm_w,
_originalPhotoLabel.mm_h);
_originalPhotoLabel.alpha = 0;
[_doneButton sizeToFit];
_doneButton.frame = CGRectMake(self.view.mm_w - _doneButton.mm_w - 12, 0, MAX(44, _doneButton.mm_w), 44);
_numberImageView.frame = CGRectMake(_doneButton.mm_x - 24 - 5, 10, 24, 24);
_numberLabel.frame = _numberImageView.frame;
if (isRTL()) {
for (UIView *subview in _naviBar.subviews) {
[subview resetFrameToFitRTL];
}
for (UIView *subview in _toolBar.subviews) {
[subview resetFrameToFitRTL];
}
}
[self layoutSelectedScrollView];
}
- (void)layoutSelectedScrollView {
CGFloat photoViewSize = 65;
CGFloat photoViewMargin = 15;
CGFloat scrollViewHeight = photoViewSize + 2 * photoViewMargin;
_selectedScrollView.frame = CGRectMake(0, _toolBar.mm_y - scrollViewHeight, self.view.mm_w, scrollViewHeight);
if (isRTL()) {
_selectedScrollView.transform = CGAffineTransformMakeRotation(M_PI);
}
}
- (void)updateSelectedScrollViewWithModels:(NSMutableArray *)selectedModels {
if (self.isSelectOriginalPhoto) {
[self showTotalPhotoBytes];
}
}
- (void)onScrollToItemAtIndex:(NSUInteger)index {
if (index < self.assetModels.count) {
TUIAssetModel *model = self.assetModels[index];
[self->_selectedScrollView switchPhoto:model];
}
}
- (BOOL)findInAllModels:(TUIAssetModel *)model findIndex:(NSUInteger *)findIndex {
for (TUIAssetModel *modelItem in self.assetModels) {
if ([modelItem.asset.localIdentifier isEqualToString:model.asset.localIdentifier]) {
*findIndex = [self.assetModels indexOfObject:modelItem];
return YES;
}
}
return NO;
}
- (BOOL)findInSelectedModels:(TUIAssetModel *)model findIndex:(NSUInteger *)findIndex {
for (TUIAssetModel *modelItem in self.selectedModels) {
if ([modelItem.asset.localIdentifier isEqualToString:model.asset.localIdentifier]) {
*findIndex = [self.selectedModels indexOfObject:modelItem];
return YES;
}
}
return NO;
}
#pragma mark - Notification
- (void)didChangeStatusBarOrientationNotification:(NSNotification *)noti {
_offsetItemCount = _collectionView.contentOffset.x / _layout.itemSize.width;
}
#pragma mark - Click Event
- (void)onSelectButtonClick:(UIButton *)selectButton {
[self selectPhoto:selectButton refreshCount:YES];
}
- (void)selectPhoto:(UIButton *)selectButton refreshCount:(BOOL)refreshCount {
TUIAssetModel *model = self.assetModels[self.currentShowIndex];
if (!selectButton.isSelected) {
// 1. select:check if over the maxImagesCount
if (self.selectedModels.count >= self.multiMediaNavVC.maxImagesCount) {
NSString *title = [NSString stringWithFormat:[NSBundle tui_localizedStringForKey:@"Select a maximum of %zd photos"], self.multiMediaNavVC.maxImagesCount];
[self.multiMediaNavVC showAlertWithTitle:title];
return;
}
else {
// 2. if not over the maxImagesCount
[self.selectedModels addObject:model];
[self->_selectedScrollView addPhoto:model];
if (self.addAssetBlock) {
self.addAssetBlock(model);
}
}
} else {
NSArray *selectedModels = [NSArray arrayWithArray:self.selectedModels];
for (TUIAssetModel *modelItem in selectedModels) {
if ([modelItem.asset.localIdentifier isEqualToString:model.asset.localIdentifier]) {
NSArray *selectedModelsTmp = [NSArray arrayWithArray:self.selectedModels];
for (NSInteger i = 0; i < selectedModelsTmp.count; i++) {
TUIAssetModel *model = selectedModelsTmp[i];
if ([model isEqual:modelItem]) {
[self.selectedModels removeObject:model];
[self->_selectedScrollView removePhoto:model];
if (self.delAssetBlock) {
self.delAssetBlock(model);
}
break;
}
}
break;
}
}
}
model.isSelected = !selectButton.isSelected;
if (model.isSelected) {
[UIView showOscillatoryAnimationWithLayer:selectButton.imageView.layer type:TUIOscillatoryAnimationToBigger];
}
[UIView showOscillatoryAnimationWithLayer:_numberImageView.layer type:TUIOscillatoryAnimationToSmaller];
if (refreshCount) {
[self refreshNaviBarAndBottomBarState];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self updateSelectedScrollViewWithModels:self.selectedModels];
[self onScrollToItemAtIndex:self.currentShowIndex];
});
}
- (void)onBackButtonClick {
[self.navigationController popViewControllerAnimated:YES];
if (self.backButtonClickBlock) {
self.backButtonClickBlock(_isSelectOriginalPhoto);
}
}
- (void)onDoneButtonClick {
// Alert user if pictures are being synced from iCloud
if (_progress > 0 && _progress < 1 && (_selectButton.isSelected || !self.selectedModels.count )) {
_alertView = [self.multiMediaNavVC showAlertWithTitle:[NSBundle tui_localizedStringForKey:@"Synchronizing photos from iCloud"]];
return;
}
// If no photo has been selected, click OK to select the currently previewed photo.
if (self.selectedModels.count == 0) {
[self selectPhoto:_selectButton refreshCount:NO];
}
if (self.doneButtonClickBlock) {
self.doneButtonClickBlock(_isSelectOriginalPhoto);
}
}
- (void)onEditPhotoButtonClick {
__weak typeof(self) weakSelf = self;
TUIAssetModel * model = [self getCurrentSelectedModel];
if (!model) {
return;
}
if (model.type == TUIAssetMediaTypeVideo) {
[[TUIImageManager defaultManager] requestVideoURLWithAsset:model.asset success:^(NSURL *videoURL) {
void (^editVideoBlock)(void) = ^(void) {
NSURL *formatUrl = videoURL;
if (model.editurl) {
formatUrl = model.editurl;
}
[[TUIMultimediaProcessor shareInstance] editVideo:weakSelf url:formatUrl complete:^(NSURL * _Nullable uri) {
NSLog(@"transcode url is %@",videoURL);
if (uri != nil) {
[self replaceCurrentSelectedModel:uri];
}
}];
};
dispatch_async(dispatch_get_main_queue(), editVideoBlock);
NSLog(@"%@",videoURL);
} failure:^(NSDictionary *info) {
}];
}
else if (model.type == TUIAssetMediaTypePhoto) {
if (model.editImage != nil) {
[self editPictureOnMainQueue:model.editImage];
} else {
[[TUIImageManager defaultManager] getOriginalPhotoDataWithAsset:model.asset
progressHandler:nil
completion:^(NSData *data, NSDictionary *info, BOOL isDegraded) {
if (!isDegraded) {
UIImage *img = [UIImage imageWithData:data];
[self editPictureOnMainQueue:img];
}
}];
}
}
}
- (void)editPictureOnMainQueue:(UIImage*) image {
__weak typeof(self) weakSelf = self;
void (^editPictureBlock)(void) = ^(void) {
[[TUIMultimediaProcessor shareInstance] editPicture:weakSelf picture:image complete:^(UIImage * _Nullable picture) {
if (picture != nil) {
[self replaceCurrentSelectedPhotoModel:picture];
}
}];
};
dispatch_async(dispatch_get_main_queue(), editPictureBlock);
}
- (void)onProvideVideo:(NSURL *)videoURL
snapshot:(NSString *)snapshotUrl
duration:(NSInteger)duration {
V2TIMMessage *message = [[V2TIMManager sharedInstance] createVideoMessage:videoURL.path type:videoURL.path.pathExtension duration:(int)duration snapshotPath:snapshotUrl];
NSDictionary *param = @{TUICore_TUIChatService_SendMessageMethod_MsgKey : message};
NSString *entranceName = [TIMConfig isClassicEntrance]?
TUICore_TUIChatService:TUICore_TUIChatService_Minimalist;
[TUICore callService:entranceName
method:TUICore_TUIChatService_SendMessageMethod param:param];
}
- (TUIAssetModel *)getCurrentSelectedModel {
TUIAssetModel *model = self.assetModels[self.currentShowIndex];
return model;
}
- (void)replaceCurrentSelectedModel:(NSURL *)uri {
TUIAssetModel * model = [self getCurrentSelectedModel];
if (model.type == TUIAssetMediaTypeVideo) {
model.editurl = uri;
model.editImage = [[TUIImageManager defaultManager] getImageWithVideoURL:uri];
if (self.refreshAssetBlock) {
self.refreshAssetBlock(model);
}
[_collectionView reloadData];
[self refreshNaviBarAndBottomBarState];
}
}
- (void)replaceCurrentSelectedPhotoModel:(UIImage *)image {
TUIAssetModel * model = [self getCurrentSelectedModel];
if (model == nil || model.type != TUIAssetMediaTypePhoto) {
return;
}
model.editImage = image;
if (self.refreshAssetBlock) {
self.refreshAssetBlock(model);
}
[_selectedScrollView freshPhoto : model];
[_collectionView reloadData];
[self refreshNaviBarAndBottomBarState];
}
- (void)onOriginalPhotoButtonClick {
_originalPhotoButton.selected = !_originalPhotoButton.isSelected;
_isSelectOriginalPhoto = _originalPhotoButton.selected;
_originalPhotoLabel.hidden = !_originalPhotoButton.isSelected;
if (_isSelectOriginalPhoto) {
[self showTotalPhotoBytes];
}
}
- (void)didTapPreviewCell {
self.isHideNaviBar = !self.isHideNaviBar;
_naviBar.hidden = self.isHideNaviBar;
_toolBar.hidden = self.isHideNaviBar;
_selectedScrollView.hidden = (self.isHideNaviBar || 0 == self.selectedModels.count);
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == _collectionView) {
CGFloat offSetWidth = scrollView.contentOffset.x;
offSetWidth = offSetWidth + ((self.view.mm_w + 20) * 0.5);
NSInteger currentShowIndex = offSetWidth / (self.view.mm_w + 20);
if (currentShowIndex < self.assetModels.count && _currentShowIndex != currentShowIndex) {
_currentShowIndex = currentShowIndex;
[self refreshNaviBarAndBottomBarState];
[self onScrollToItemAtIndex:self.currentShowIndex];
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"photoPreviewCollectionViewDidScroll" object:nil];
}
else {
}
}
#pragma mark - UICollectionViewDataSource && Delegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.assetModels.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
TUIAssetModel *model = self.assetModels[indexPath.item];
TUIAssetPreviewCell *cell;
if (TUIAssetMediaTypeVideo == model.type) {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TUIVideoPreviewCell" forIndexPath:indexPath];
TUIVideoPreviewCell *currentCell = (TUIVideoPreviewCell *)cell;
@weakify(self)
currentCell.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) {
@strongify(self)
model.iCloudFailed = isSyncFailed;
[self didICloudSyncStatusChanged:model];
};
} else if (TUIAssetMediaTypePhotoGif == model.type) {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TUIGifPreviewCell" forIndexPath:indexPath];
TUIGifPreviewCell *currentCell = (TUIGifPreviewCell *)cell;
@weakify(self)
currentCell.previewView.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) {
@strongify(self)
model.iCloudFailed = isSyncFailed;
[self didICloudSyncStatusChanged:model];
};
} else {
NSString *reuseId = model.type == TUIAssetMediaTypePhotoGif ? @"TUIPhotoPreviewCellGIF" : @"TUIPhotoPreviewCell";
cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseId forIndexPath:indexPath];
TUIPhotoPreviewCell *photoPreviewCell = (TUIPhotoPreviewCell *)cell;
@weakify(self)
[photoPreviewCell setImageProgressUpdateBlock:^(double progress) {
@strongify(self)
self.progress = progress;
if (progress >= 1) {
if (self.isSelectOriginalPhoto) [self showTotalPhotoBytes];
if (self.alertView && [self->_collectionView.visibleCells containsObject:photoPreviewCell]) {
[self.alertView dismissViewControllerAnimated:YES completion:^{
self.alertView = nil;
[self onDoneButtonClick];
}];
}
}
}];
photoPreviewCell.previewView.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) {
@strongify(self)
model.iCloudFailed = isSyncFailed;
[self didICloudSyncStatusChanged:model];
};
}
cell.model = model;
@weakify(self)
[cell setSingleTapGestureBlock:^{
@strongify(self)
[self didTapPreviewCell];
}];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
if ([cell isKindOfClass:[TUIPhotoPreviewCell class]]) {
[(TUIPhotoPreviewCell *)cell recoverSubviews];
}
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
if ([cell isKindOfClass:[TUIPhotoPreviewCell class]]) {
[(TUIPhotoPreviewCell *)cell recoverSubviews];
} else if ([cell isKindOfClass:[TUIVideoPreviewCell class]]) {
TUIVideoPreviewCell *videoCell = (TUIVideoPreviewCell *)cell;
if (videoCell.player && videoCell.player.rate != 0.0) {
[videoCell pausePlayerAndShowNaviBar];
}
}
}
#pragma mark - Private Method
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)refreshNaviBarAndBottomBarState {
TUIAssetModel *model = self.assetModels[self.currentShowIndex];
_selectButton.selected = model.isSelected;
[self refreshSelectButtonImageViewContentMode];
_indexLabel.hidden = YES;
_numberLabel.text = [NSString stringWithFormat:@"%zd",self.selectedModels.count];
_numberImageView.hidden = (self.selectedModels.count <= 0 || _isHideNaviBar || _isCropImage);
_numberLabel.hidden = (self.selectedModels.count <= 0 || _isHideNaviBar || _isCropImage);
_originalPhotoButton.selected = _isSelectOriginalPhoto;
_originalPhotoLabel.hidden = !_originalPhotoButton.isSelected;
if (_isSelectOriginalPhoto) [self showTotalPhotoBytes];
if (!_isHideNaviBar) {
_originalPhotoButton.hidden = NO;
if (_isSelectOriginalPhoto) _originalPhotoLabel.hidden = NO;
}
if (model.type == TUIAssetMediaTypeVideo || model.type == TUIAssetMediaTypePhoto) {
[_editPhotoButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
}
else {
[_editPhotoButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
}
_doneButton.hidden = NO;
_selectButton.hidden = NO;
[self didICloudSyncStatusChanged:model];
}
- (void)refreshSelectButtonImageViewContentMode {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self->_selectButton.imageView.image.size.width <= 27) {
self->_selectButton.imageView.contentMode = UIViewContentModeCenter;
} else {
self->_selectButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
}
});
}
- (void)didICloudSyncStatusChanged:(TUIAssetModel *)model{
dispatch_async(dispatch_get_main_queue(), ^{
TUIAssetModel *currentModel = self.assetModels[self.currentShowIndex];
if (self.selectedModels.count <= 0) {
self->_doneButton.enabled = !currentModel.iCloudFailed;
} else {
self->_doneButton.enabled = YES;
}
self->_selectButton.hidden = currentModel.iCloudFailed;
if (currentModel.iCloudFailed) {
self->_originalPhotoButton.hidden = YES;
self->_originalPhotoLabel.hidden = YES;
}
});
}
- (void)showTotalPhotoBytes {
if (0 == self.selectedModels.count) {
self->_originalPhotoLabel.text = @"";
return;
}
#if 0
[[TUIImageManager defaultManager] getPhotosTotalBytes:self.selectedModels completion:^(NSString *totalBytes) {
dispatch_async(dispatch_get_main_queue(), ^{
self->_originalPhotoLabel.text = [NSString stringWithFormat:@"%@ %@",[NSBundle tui_localizedStringForKey:@"TUIAlbumSelectorTotal"],totalBytes];
[self->_originalPhotoLabel sizeToFit];
});
}];
#endif
}
- (NSInteger)currentShowIndex {
return [TUICommonTools tui_isRightToLeftLayout] ? self.assetModels.count - _currentShowIndex - 1 : _currentShowIndex;
}
- (void)onClick:(NSInteger)index model:(TUIAssetModel *)model;{
NSUInteger findIndex = 0;
BOOL findModel = [self findInAllModels:model findIndex:&findIndex];
if (findModel && _collectionView) {
[_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:findIndex inSection:0]
atScrollPosition:(UICollectionViewScrollPositionLeft)animated:NO];
}
else {
//Other bucket
}
}
@end

View File

@@ -0,0 +1,25 @@
//
// TUIRequestOperation.h
// TUIRequestOperation
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Photos/Photos.h>
NS_ASSUME_NONNULL_BEGIN
@interface TUIRequestOperation : NSOperation
typedef void(^TUIRequestCompletedBlock)(UIImage *photo, NSDictionary *info);
typedef void(^TUIRequestProgressBlock)(double progress, NSError *error, BOOL *stop, NSDictionary *info);
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
- (instancetype)initWithAsset:(PHAsset *)asset completed:(TUIRequestCompletedBlock)completedBlock progress:(TUIRequestProgressBlock)progressBlock;
- (void)done;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,83 @@
//
// TUIRequestOperation.m
// TUIRequestOperation
//
// Created by lynx on 2024/8/21.
// Copyright © 2024 Tencent. All rights reserved.
//
#import "TUIRequestOperation.h"
#import <TUIMultimediaCore/TUIImageManager.h>
@interface TUIRequestOperation()
@property (nonatomic, copy, nullable) TUIRequestCompletedBlock completedBlock;
@property (nonatomic, copy, nullable) TUIRequestProgressBlock progressBlock;
@property (nonatomic, strong, nullable) PHAsset *asset;
@end
@implementation TUIRequestOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
- (instancetype)initWithAsset:(PHAsset *)asset completed:(TUIRequestCompletedBlock)completedBlock progress:(TUIRequestProgressBlock)progressBlock {
self = [super init];
self.asset = asset;
self.completedBlock = completedBlock;
self.progressBlock = progressBlock;
_executing = NO;
_finished = NO;
return self;
}
- (void)start {
self.executing = YES;
[[TUIImageManager defaultManager] getPhotoWithAsset:self.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
if (isDegraded) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
if (self.completedBlock) {
self.completedBlock(photo, info);
}
[self done];
});
} progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.progressBlock) {
self.progressBlock(progress, error, stop, info);
}
});
} networkAccessAllowed:YES];
}
- (void)done {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.finished = YES;
self.executing = NO;
[self reset];
});
}
- (void)reset {
self.asset = nil;
self.completedBlock = nil;
self.progressBlock = nil;
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isAsynchronous {
return YES;
}
@end