提交
This commit is contained in:
18
TUIKit/TUIMultimediaPlugin/Common/NSArray+Functional.h
Normal file
18
TUIKit/TUIMultimediaPlugin/Common/NSArray+Functional.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSArray <__covariant ObjectType>(Functional)
|
||||
|
||||
- (NSArray *)tui_multimedia_map:(id (^)(ObjectType))f;
|
||||
- (NSArray *)tui_multimedia_mapWithIndex:(id (^)(ObjectType, NSUInteger))f;
|
||||
|
||||
- (NSArray<ObjectType> *)tui_multimedia_filter:(BOOL (^)(ObjectType))f;
|
||||
|
||||
+ (NSArray<ObjectType> *)tui_multimedia_arrayWithArray:(NSArray<ObjectType> *)array append:(NSArray<ObjectType> *)append;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
40
TUIKit/TUIMultimediaPlugin/Common/NSArray+Functional.m
Normal file
40
TUIKit/TUIMultimediaPlugin/Common/NSArray+Functional.m
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "NSArray+Functional.h"
|
||||
|
||||
@implementation NSArray (Functional)
|
||||
|
||||
- (NSArray *)tui_multimedia_map:(id (^)(id))f {
|
||||
NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count];
|
||||
for (id x in self) {
|
||||
[array addObject:f(x)];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
- (NSArray *)tui_multimedia_mapWithIndex:(id (^)(id, NSUInteger))f {
|
||||
const NSUInteger count = self.count;
|
||||
NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
|
||||
for (NSInteger i = 0; i < count; i++) {
|
||||
[array addObject:f(self[i], i)];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
- (NSArray *)tui_multimedia_filter:(BOOL (^)(id))f {
|
||||
NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count];
|
||||
for (id x in self) {
|
||||
if (f(x)) {
|
||||
[array addObject:x];
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
+ (NSArray *)tui_multimedia_arrayWithArray:(NSArray *)array append:(NSArray *)append {
|
||||
NSMutableArray *res = [NSMutableArray arrayWithArray:array];
|
||||
for (id x in append) {
|
||||
[res addObject:x];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@end
|
||||
27
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaCommon.h
Normal file
27
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaCommon.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <TXLiteAVSDK_Professional/TXLiveRecordTypeDef.h>
|
||||
#import <TXLiteAVSDK_Professional/TXVideoEditerTypeDef.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#define TUIMultimediaConstCGSize(w, h) ((CGSize){(CGFloat)(w), (CGFloat)(h)})
|
||||
|
||||
static const int TUIMultimediaEditBitrate = 12000;
|
||||
|
||||
@interface TUIMultimediaCommon : NSObject
|
||||
@property(class, readonly) NSBundle *bundle;
|
||||
@property(class, readonly) NSBundle *localizableBundle;
|
||||
+ (nullable UIImage *)bundleImageByName:(NSString *)name;
|
||||
+ (nullable UIImage *)bundleRawImageByName:(NSString *)name;
|
||||
+ (NSString *)localizedStringForKey:(NSString *)key;
|
||||
+ (UIColor *)colorFromHex:(NSString *)hex;
|
||||
+ (NSArray<NSString *> *)sortedBundleResourcesIn:(NSString *)directory withExtension:(NSString *)ext;
|
||||
|
||||
+ (NSURL *)getURLByResourcePath:(NSString *)path;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
75
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaCommon.m
Normal file
75
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaCommon.m
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaCommon.h"
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPersistence.h"
|
||||
|
||||
static NSString *const BundleResourceUrlPrefix = @"file:///asset/";
|
||||
|
||||
@implementation TUIMultimediaCommon
|
||||
|
||||
@dynamic bundle;
|
||||
|
||||
+ (NSBundle *)bundle {
|
||||
NSURL *url = [[NSBundle mainBundle] URLForResource:@"TUIMultimedia" withExtension:@"bundle"];
|
||||
return [NSBundle bundleWithURL:url];
|
||||
}
|
||||
|
||||
+ (NSBundle *)localizableBundle {
|
||||
NSURL *url = [[NSBundle mainBundle] URLForResource:@"TUIMultimediaLocalizable" withExtension:@"bundle"];
|
||||
return [NSBundle bundleWithURL:url];
|
||||
}
|
||||
|
||||
+ (UIImage *)bundleImageByName:(NSString *)name {
|
||||
return [UIImage imageNamed:name inBundle:self.bundle compatibleWithTraitCollection:nil];
|
||||
}
|
||||
|
||||
+ (UIImage *)bundleRawImageByName:(NSString *)name {
|
||||
NSString *path = [NSString stringWithFormat:@"%@/%@", [self bundle].resourcePath, name];
|
||||
return [UIImage imageWithContentsOfFile:path];
|
||||
}
|
||||
|
||||
+ (NSString *)localizedStringForKey:(NSString *)key {
|
||||
NSString *lang = [TUIGlobalization getPreferredLanguage];
|
||||
NSString *path = [self.localizableBundle pathForResource:lang ofType:@"lproj"];
|
||||
if (path == nil) {
|
||||
path = [self.localizableBundle pathForResource:@"en" ofType:@"lproj"];
|
||||
}
|
||||
NSBundle *langBundle = [NSBundle bundleWithPath:path];
|
||||
return [langBundle localizedStringForKey:key value:nil table:nil];
|
||||
}
|
||||
|
||||
+ (UIColor *)colorFromHex:(NSString *)hex {
|
||||
return [UIColor tui_colorWithHex:hex];
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)sortedBundleResourcesIn:(NSString *)directory withExtension:(NSString *)ext {
|
||||
NSArray<NSString *> *res = [TUIMultimediaCommon.bundle pathsForResourcesOfType:ext inDirectory:directory];
|
||||
NSString *basePath = TUIMultimediaCommon.bundle.resourcePath;
|
||||
res = [res tui_multimedia_map:^NSString *(NSString *path) {
|
||||
return [path substringFromIndex:basePath.length + 1];
|
||||
}];
|
||||
return [res sortedArrayUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
|
||||
return [a compare:b];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (NSURL *)getURLByResourcePath:(NSString *)path {
|
||||
if (path == nil) {
|
||||
return nil;
|
||||
}
|
||||
if (![path startsWith:BundleResourceUrlPrefix]) {
|
||||
NSURL *url = [NSURL URLWithString:path];
|
||||
if (url.scheme == nil) {
|
||||
return [NSURL fileURLWithPath:path];
|
||||
}
|
||||
return url;
|
||||
}
|
||||
NSURL *bundleUrl = [[self bundle] resourceURL];
|
||||
NSURL *url = [[NSURL alloc] initWithString:[path substringFromIndex:BundleResourceUrlPrefix.length] relativeToURL:bundleUrl];
|
||||
return url;
|
||||
}
|
||||
@end
|
||||
32
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaConfig.h
Normal file
32
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaConfig.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Created by eddardliu on 2024/10/21.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaConfig : NSObject
|
||||
+ (instancetype)sharedInstance;
|
||||
- (void)setConfig:(NSString*)jsonString;
|
||||
- (BOOL)isSupportRecordBeauty;
|
||||
- (BOOL)isSupportRecordAspect;
|
||||
- (BOOL)isSupportRecordTorch;
|
||||
- (BOOL)isSupportRecordScrollFilter;
|
||||
- (BOOL)isSupportVideoEditGraffiti;
|
||||
- (BOOL)isSupportVideoEditPaster;
|
||||
- (BOOL)isSupportVideoEditSubtitle;
|
||||
- (BOOL)isSupportVideoEditBGM;
|
||||
- (BOOL)isSupportPictureEditMosaic;
|
||||
- (BOOL)isSupportPictureEditGraffiti;
|
||||
- (BOOL)isSupportPictureEditPaster;
|
||||
- (BOOL)isSupportPictureEditSubtitle;
|
||||
- (BOOL)isSupportPictureEditCrop;
|
||||
- (UIColor*)getThemeColor;
|
||||
- (int)getMaxRecordDurationMs;
|
||||
- (int)getMinRecordDurationMs;
|
||||
- (int)getVideoQuality;
|
||||
- (NSString *)getPicturePasterConfigFilePath;
|
||||
- (NSString *)getBGMConfigFilePath;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
169
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaConfig.m
Normal file
169
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaConfig.m
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Created by eddardliu on 2024/10/21.
|
||||
|
||||
#import <TUICore/UIColor+TUIHexColor.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
#import "TUIMultimediaConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
|
||||
#define DEFAULT_CONFIG_FILE @"config/default_config"
|
||||
|
||||
@interface TUIMultimediaConfig() {
|
||||
NSDictionary * _jsonDicFromFile;
|
||||
NSDictionary * _jsonDicFromSetting;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaConfig
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static TUIMultimediaConfig *sharedInstance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init{
|
||||
self = [super init];
|
||||
NSData *jsonData = [NSData dataWithContentsOfFile:[TUIMultimediaCommon.bundle pathForResource:DEFAULT_CONFIG_FILE ofType:@"json"]];
|
||||
NSError *err = nil;
|
||||
_jsonDicFromFile = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
|
||||
if (err || ![_jsonDicFromFile isKindOfClass:[NSDictionary class]]) {
|
||||
NSLog(@"[TUIMultimedia] Json parse failed: %@", err);
|
||||
_jsonDicFromFile = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setConfig:(NSString*)jsonString {
|
||||
_jsonDicFromFile = nil;
|
||||
if (jsonString == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *err = nil;
|
||||
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
if (jsonData) {
|
||||
_jsonDicFromFile = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
|
||||
if (err || ![_jsonDicFromFile isKindOfClass:[NSDictionary class]]) {
|
||||
NSLog(@"[TUIMultimediaConfig setConfig] Json parse failed: %@", err);
|
||||
_jsonDicFromFile = nil;
|
||||
}
|
||||
} else {
|
||||
NSLog(@"Error converting string to data");
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isSupportRecordBeauty {
|
||||
return [self getBoolFromDic:@"support_record_beauty" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportRecordAspect {
|
||||
return [self getBoolFromDic:@"support_record_aspect" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportRecordTorch {
|
||||
return [self getBoolFromDic:@"support_record_torch" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportRecordScrollFilter {
|
||||
return [self getBoolFromDic:@"support_record_scroll_filter" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportVideoEditGraffiti {
|
||||
return [self getBoolFromDic:@"support_video_edit_graffiti" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportVideoEditPaster {
|
||||
return [self getBoolFromDic:@"support_video_edit_paster" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportVideoEditSubtitle {
|
||||
return [self getBoolFromDic:@"support_video_edit_subtitle" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportVideoEditBGM {
|
||||
return [self getBoolFromDic:@"support_video_edit_bgm" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportPictureEditMosaic {
|
||||
return [self getBoolFromDic:@"support_picture_edit_mosaic" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportPictureEditGraffiti {
|
||||
return [self getBoolFromDic:@"support_picture_edit_graffiti" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportPictureEditPaster {
|
||||
return [self getBoolFromDic:@"support_picture_edit_paster" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportPictureEditSubtitle {
|
||||
return [self getBoolFromDic:@"support_picture_edit_subtitle" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (BOOL)isSupportPictureEditCrop {
|
||||
return [self getBoolFromDic:@"support_picture_edit_crop" defaultValue:YES];
|
||||
}
|
||||
|
||||
- (UIColor *)getThemeColor {
|
||||
return TIMCommonDynamicColor(@"primary_theme_color", @"#147AFF");
|
||||
}
|
||||
|
||||
- (int)getVideoQuality {
|
||||
return [self getIntFromDic:@"video_quality" defaultValue:2];
|
||||
}
|
||||
|
||||
- (int)getMaxRecordDurationMs {
|
||||
return [self getIntFromDic:@"max_record_duration_ms" defaultValue:15000];
|
||||
}
|
||||
|
||||
- (int)getMinRecordDurationMs {
|
||||
return [self getIntFromDic:@"min_record_duration_ms" defaultValue:2000];
|
||||
}
|
||||
|
||||
- (NSString *)getPicturePasterConfigFilePath {
|
||||
return [self getStringFromDic:@"picture_paster_config_file_path" defaultValue:@"config/picture_paster_data"];
|
||||
}
|
||||
|
||||
- (NSString *)getBGMConfigFilePath {
|
||||
return [self getStringFromDic:@"bgm_config_file_path" defaultValue:@"config/bgm_data"];
|
||||
}
|
||||
|
||||
-(BOOL) getBoolFromDic:(NSString*) dicKey defaultValue:(BOOL) defaultValue{
|
||||
if (_jsonDicFromSetting != nil) {
|
||||
return [_jsonDicFromSetting[dicKey] caseInsensitiveCompare:@"true"] == NSOrderedSame;
|
||||
}
|
||||
|
||||
if (_jsonDicFromFile != nil) {
|
||||
return [_jsonDicFromFile[dicKey] caseInsensitiveCompare:@"true"] == NSOrderedSame;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
-(int) getIntFromDic:(NSString*) dicKey defaultValue:(int) defaultValue{
|
||||
if (_jsonDicFromSetting != nil) {
|
||||
return [(NSNumber *)_jsonDicFromFile[dicKey] intValue];
|
||||
}
|
||||
|
||||
if (_jsonDicFromFile != nil) {
|
||||
return [(NSNumber *)_jsonDicFromFile[dicKey] intValue];
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
-(NSString*) getStringFromDic:(NSString*) dicKey defaultValue:(NSString*) defaultValue{
|
||||
if (_jsonDicFromSetting != nil) {
|
||||
return _jsonDicFromSetting[dicKey];
|
||||
}
|
||||
|
||||
if (_jsonDicFromFile != nil) {
|
||||
return _jsonDicFromFile[dicKey];
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@end
|
||||
15
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaConstant.h
Normal file
15
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaConstant.h
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#define VIDEO_EDIT_RESULT_CODE_NO_EDIT 1
|
||||
#define VIDEO_EDIT_RESULT_CODE_GENERATE_SUCCESS 0
|
||||
#define VIDEO_EDIT_RESULT_CODE_CANCEL -1
|
||||
#define VIDEO_EDIT_RESULT_CODE_GENERATE_FAIL -2
|
||||
|
||||
#define PHOTO_EDIT_RESULT_CODE_NO_EDIT 1
|
||||
#define PHOTO_EDIT_RESULT_CODE_GENERATE_SUCCESS 0
|
||||
#define PHOTO_EDIT_RESULT_CODE_CANCEL -1
|
||||
#define PHOTO_EDIT_RESULT_CODE_GENERATE_FAIL -2
|
||||
|
||||
#define SOURCE_TYPE_RECORD 1
|
||||
#define SOURCE_TYPE_ALBUM 2
|
||||
20
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaGeometry.h
Normal file
20
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaGeometry.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
static CGFloat Vec2Len(CGPoint v) { return sqrt(v.x * v.x + v.y * v.y); }
|
||||
|
||||
static CGPoint Vec2Mul(CGPoint v, CGFloat k) { return CGPointMake(v.x * k, v.y * k); }
|
||||
|
||||
static CGPoint Vec2AddVector(CGPoint v, CGPoint va) { return CGPointMake(v.x + va.x, v.y + va.y); }
|
||||
|
||||
static CGPoint Vec2AddOffset(CGPoint v, CGFloat k) { return CGPointMake(v.x + k, v.y + k); }
|
||||
|
||||
static CGFloat Vec2Degree(CGPoint v1, CGPoint v2) { return atan2(v2.y, v2.x) - atan2(v1.y, v1.x); }
|
||||
|
||||
static CGPoint Vec2Rotate(CGPoint p, CGFloat r) {
|
||||
CGFloat s = sin(r);
|
||||
CGFloat c = cos(r);
|
||||
return CGPointMake(p.x * c - p.y * s, p.x * s + p.y * c);
|
||||
}
|
||||
17
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaImagePicker.h
Normal file
17
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaImagePicker.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^TUIMultimediaImagePickerCallback)(UIImage *_Nullable image);
|
||||
|
||||
@interface TUIMultimediaImagePicker : NSObject
|
||||
@property(nullable, nonatomic) TUIMultimediaImagePickerCallback callback;
|
||||
- (instancetype)init;
|
||||
- (void)presentOn:(UIViewController *)viewController;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
38
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaImagePicker.m
Normal file
38
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaImagePicker.m
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaImagePicker.h"
|
||||
@interface TUIMultimediaImagePicker () <UINavigationControllerDelegate, UIImagePickerControllerDelegate> {
|
||||
UIImagePickerController *_pickerVC;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaImagePicker
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_pickerVC = [[UIImagePickerController alloc] init];
|
||||
_pickerVC.delegate = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)presentOn:(UIViewController *)viewController {
|
||||
[viewController presentViewController:_pickerVC animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey, id> *)info {
|
||||
[picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
if (_callback != nil) {
|
||||
_callback([info objectForKey:UIImagePickerControllerOriginalImage]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
|
||||
[picker.presentingViewController dismissViewControllerAnimated:YES completion:nil];
|
||||
if (_callback != nil) {
|
||||
_callback(nil);
|
||||
}
|
||||
}
|
||||
@end
|
||||
28
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaImageUtil.h
Normal file
28
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaImageUtil.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaImageUtil : NSObject
|
||||
// 对View截图
|
||||
+ (UIImage *)imageFromView:(UIView *)view;
|
||||
|
||||
// 对View截图,带旋转
|
||||
+ (UIImage *)imageFromView:(UIView *)view withRotate:(CGFloat)rotate;
|
||||
|
||||
// 以另一图片为模版,创建一个形状大小相同,但颜色不同的图片,保留透明通道,不保留灰度信息
|
||||
+ (UIImage *)imageFromImage:(UIImage *)img withTintColor:(UIColor *)tintColor;
|
||||
|
||||
+ (UIImage *)resizeImage:(UIImage *)img toSize:(CGSize)size;
|
||||
|
||||
+ (UIImage *)rotateImage:(UIImage *)img angle:(float)angle;
|
||||
|
||||
+ (UIImage *)imageWithColor:(UIColor *)color; // size: 1x1
|
||||
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size;
|
||||
+ (UIImage *)createBlueCircleWithWhiteBorder:(CGSize)size withColor:(UIColor*) color;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
135
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaImageUtil.m
Normal file
135
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaImageUtil.m
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaImageUtil.h"
|
||||
|
||||
@implementation TUIMultimediaImageUtil
|
||||
+ (UIImage *)imageFromView:(UIView *)view {
|
||||
UIGraphicsBeginImageContext(view.bounds.size);
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
[view.layer renderInContext:ctx];
|
||||
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return img;
|
||||
}
|
||||
|
||||
+ (UIImage *)imageFromView:(UIView *)view withRotate:(CGFloat)rotate {
|
||||
// view旋转后占用的矩形大小--最终画布大小
|
||||
CGSize finalSize = CGSizeMake(CGRectGetWidth(view.frame), CGRectGetHeight(view.frame));
|
||||
// view的原始大小
|
||||
CGSize originSize = CGSizeMake(CGRectGetWidth(view.bounds), CGRectGetHeight(view.bounds));
|
||||
|
||||
UIGraphicsBeginImageContext(finalSize);
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
// 根据画布大小,先移动到中心点
|
||||
CGContextTranslateCTM(ctx, finalSize.width / 2, finalSize.height / 2);
|
||||
// 旋转坐标系
|
||||
CGContextRotateCTM(ctx, rotate);
|
||||
// 根据原始大小确定绘制view的起始位置
|
||||
CGContextTranslateCTM(ctx, -originSize.width / 2, -originSize.height / 2);
|
||||
[view.layer renderInContext:ctx];
|
||||
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return img;
|
||||
}
|
||||
|
||||
+ (UIImage *)simpleImageFromImage:(UIImage *)img withTintColor:(UIColor *)tintColor {
|
||||
// We want to keep alpha, set opaque to NO; Use 0.0f for scale to use the scale factor of the
|
||||
// device’s main screen.
|
||||
UIGraphicsBeginImageContextWithOptions(img.size, NO, 0.0f);
|
||||
[tintColor setFill];
|
||||
CGRect bounds = CGRectMake(0, 0, img.size.width, img.size.height);
|
||||
UIRectFill(bounds);
|
||||
|
||||
// Draw the tinted image in context
|
||||
[img drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:1.0f];
|
||||
|
||||
UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return tintedImage;
|
||||
}
|
||||
|
||||
+ (UIImage *)imageFromImage:(UIImage *)img withTintColor:(UIColor *)tintColor {
|
||||
UIImage *imgWithTintColor = [self simpleImageFromImage:img withTintColor:tintColor];
|
||||
if (img.imageAsset == nil) {
|
||||
return imgWithTintColor;
|
||||
}
|
||||
UITraitCollection *const scaleTraitCollection = [UITraitCollection currentTraitCollection];
|
||||
UITraitCollection *const darkUnscaledTraitCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
|
||||
UITraitCollection *const darkScaledTraitCollection =
|
||||
[UITraitCollection traitCollectionWithTraitsFromCollections:@[ scaleTraitCollection, darkUnscaledTraitCollection ]];
|
||||
|
||||
UIImage *imageDark = [img.imageAsset imageWithTraitCollection:darkScaledTraitCollection];
|
||||
if (img != imageDark) {
|
||||
[imgWithTintColor.imageAsset registerImage:[self simpleImageFromImage:imageDark withTintColor:tintColor] withTraitCollection:darkScaledTraitCollection];
|
||||
}
|
||||
|
||||
return imgWithTintColor;
|
||||
}
|
||||
|
||||
+ (UIImage *)resizeImage:(UIImage *)img toSize:(CGSize)size {
|
||||
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
|
||||
return [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
|
||||
[img drawInRect:CGRectMake(0, 0, size.width, size.height)];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (UIImage *)rotateImage:(UIImage *)image angle:(float)angle {
|
||||
CGFloat radians = angle * (M_PI / 180.0);
|
||||
CGSize size = image.size;
|
||||
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
|
||||
UIImage *rotatedImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *context) {
|
||||
CGContextRef cgContext = context.CGContext;
|
||||
CGContextTranslateCTM(cgContext, size.width / 2, size.height / 2);
|
||||
CGContextRotateCTM(cgContext, radians);
|
||||
CGContextTranslateCTM(cgContext, -size.width / 2, -size.height / 2);
|
||||
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
|
||||
}];
|
||||
|
||||
return rotatedImage;
|
||||
}
|
||||
|
||||
|
||||
+ (UIImage *)imageWithColor:(UIColor *)color {
|
||||
return [self imageWithColor:color size:CGSizeMake(1, 1)];
|
||||
}
|
||||
|
||||
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size {
|
||||
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
|
||||
return [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
|
||||
[color set];
|
||||
[rendererContext fillRect:CGRectMake(0, 0, size.width, size.height)];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (UIImage *)createBlueCircleWithWhiteBorder : (CGSize)size withColor:(UIColor*) color{
|
||||
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (!context) return nil;
|
||||
|
||||
|
||||
CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor);
|
||||
|
||||
|
||||
CGFloat centerX = size.width / 2;
|
||||
CGFloat centerY = size.width / 2;
|
||||
CGFloat radius = size.width / 2;
|
||||
|
||||
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
|
||||
CGContextSetLineWidth(context, 0);
|
||||
CGContextAddArc(context, centerX, centerY, radius, 0, 2 * M_PI, 0);
|
||||
CGContextDrawPath(context, kCGPathFillStroke);
|
||||
|
||||
|
||||
CGContextSetFillColorWithColor(context, color.CGColor);
|
||||
CGContextSetLineWidth(context, 0);
|
||||
CGContextAddArc(context, centerX, centerY, 5, 0, 2 * M_PI, 0);
|
||||
CGContextDrawPath(context, kCGPathFillStroke);
|
||||
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return image;
|
||||
}
|
||||
@end
|
||||
18
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaPersistence.h
Normal file
18
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaPersistence.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
持久化工具类
|
||||
所有路径参数均使用相对路径
|
||||
*/
|
||||
@interface TUIMultimediaPersistence : NSObject
|
||||
+ (NSString *)basePath;
|
||||
+ (BOOL)saveData:(NSData *)data toFile:(NSString *)file error:(NSError **_Nullable)error;
|
||||
+ (nullable NSData *)loadDataFromFile:(NSString *)file error:(NSError **_Nullable)error;
|
||||
+ (BOOL)removeFile:(NSString *)file error:(NSError **_Nullable)error;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
51
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaPersistence.m
Normal file
51
TUIKit/TUIMultimediaPlugin/Common/TUIMultimediaPersistence.m
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPersistence.h"
|
||||
#import <TUICore/NSString+TUIUtil.h>
|
||||
|
||||
#define PASS_ERROR_WITH_LOG(errsrc, errdst, retval) \
|
||||
do { \
|
||||
if (errsrc != nil) { \
|
||||
NSLog(@"[TUIMultimedia] %s error: %@", __func__, errsrc.localizedDescription); \
|
||||
if (errdst != nil) { \
|
||||
*errdst = errsrc; \
|
||||
} \
|
||||
return retval; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static NSString *const BasePath = @"TUIMultimediaPlugin";
|
||||
|
||||
@implementation TUIMultimediaPersistence
|
||||
+ (NSString *)basePath {
|
||||
NSString *res = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
|
||||
return [res stringByAppendingPathComponent:BasePath];
|
||||
}
|
||||
|
||||
+ (BOOL)saveData:(NSData *)data toFile:(NSString *)file error:(NSError **)error {
|
||||
NSAssert([file startsWith:self.basePath], @"Assert that file path should based on BasePath");
|
||||
NSError *err = nil;
|
||||
[NSFileManager.defaultManager createDirectoryAtPath:[file stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:&err];
|
||||
PASS_ERROR_WITH_LOG(err, error, NO);
|
||||
[data writeToFile:file options:NSDataWritingAtomic error:&err];
|
||||
PASS_ERROR_WITH_LOG(err, error, NO);
|
||||
return file;
|
||||
}
|
||||
|
||||
+ (nullable NSData *)loadDataFromFile:(NSString *)file error:(NSError **)error {
|
||||
NSAssert([file startsWith:self.basePath], @"Assert that file path should based on BasePath");
|
||||
NSError *err = nil;
|
||||
NSData *res = [NSData dataWithContentsOfFile:file options:0 error:&err];
|
||||
PASS_ERROR_WITH_LOG(err, error, nil);
|
||||
return res;
|
||||
}
|
||||
|
||||
+ (BOOL)removeFile:(NSString *)file error:(NSError **_Nullable)error {
|
||||
NSAssert([file startsWith:self.basePath], @"Assert that file path should based on BasePath");
|
||||
NSError *err = nil;
|
||||
BOOL res = [NSFileManager.defaultManager removeItemAtPath:file error:&err];
|
||||
PASS_ERROR_WITH_LOG(err, error, nil);
|
||||
return res;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaSignatureChecker : NSObject
|
||||
+ (instancetype)shareInstance;
|
||||
- (void)startUpdateSignature:(void (^)(void)) UpdateSignatureSuccessful;
|
||||
- (BOOL)isFunctionSupport;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSignatureChecker.h"
|
||||
#import <ImSDK_Plus/V2TIMManager.h>
|
||||
#import <TUICore/TUIGlobalization.h>
|
||||
#import <TUICore/TUILogin.h>
|
||||
#import <TXLiteAVSDK_Professional/TXLiteAVSDK.h>
|
||||
#import <objc/runtime.h>
|
||||
#import "NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPersistence.h"
|
||||
|
||||
#define SCHEDULE_UPDATE_SIGNATURE_INTERVAL_WHEN_START 0.f
|
||||
#define SCHEDULE_UPDATE_SIGNATURE_INTERVAL_WHEN_SUCCESS 3600 * 1000.0f
|
||||
#define SCHEDULE_UPDATE_SIGNATURE_INTERVAL_WHEN_FAIL 1000.0f
|
||||
#define SCHEDULE_UPDATE_SIGNATURE_RETRY_TIMES 3
|
||||
|
||||
@interface TUIMultimediaSignatureChecker () {
|
||||
NSString* _signature;
|
||||
NSInteger _expiredTime;
|
||||
NSTimer* _timer;
|
||||
void (^_updateSignatureSuccess)(void);
|
||||
}
|
||||
@property (nonatomic, assign) NSInteger retryCount;
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaSignatureChecker
|
||||
|
||||
+ (instancetype)shareInstance {
|
||||
static id instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[self alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_retryCount = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)startUpdateSignature:(void (^)(void))updateSignatureSuccessful {
|
||||
NSLog(@"TUIMultimediaSignatureChecker startUpdateSignature");
|
||||
_updateSignatureSuccess = updateSignatureSuccessful;
|
||||
[self scheduleUpdateSignature:SCHEDULE_UPDATE_SIGNATURE_INTERVAL_WHEN_START];
|
||||
}
|
||||
|
||||
- (void)updateSignatureIfNeed {
|
||||
if ([NSDate.now timeIntervalSince1970] < _expiredTime) {
|
||||
[self scheduleUpdateSignature:(_expiredTime - [NSDate.now timeIntervalSince1970]) *1000];
|
||||
return;
|
||||
}
|
||||
[V2TIMManager.sharedInstance callExperimentalAPI:@"getVideoEditSignature"
|
||||
param:nil
|
||||
succ:^(NSObject *result) {
|
||||
if (result == nil || ![result isKindOfClass:NSDictionary.class]) {
|
||||
NSLog(@"getVideoEditSignature: data = nil");
|
||||
return;
|
||||
}
|
||||
NSDictionary *data = (NSDictionary *)result;
|
||||
NSLog(@"getVideoEditSignature: data = %@", data);
|
||||
NSNumber *expiredTime = data[@"expired_time"];
|
||||
NSString *signature = data[@"signature"];
|
||||
if (![expiredTime isKindOfClass:NSNumber.class]) {
|
||||
NSLog(@"getVideoEditSignature: expiredTime type error");
|
||||
return;
|
||||
}
|
||||
if (![signature isKindOfClass:NSString.class]) {
|
||||
NSLog(@"getVideoEditSignature: signature type error");
|
||||
return;
|
||||
}
|
||||
self.retryCount = 0;
|
||||
self->_expiredTime = [expiredTime integerValue];
|
||||
self->_signature = signature;
|
||||
NSLog(@"getVideoEditSignature: succeed. signature=%@, expiredTime=%@", self->_signature, @(self->_expiredTime));
|
||||
[self setSignatureToSDK];
|
||||
[self scheduleUpdateSignature:([expiredTime integerValue] - [NSDate.now timeIntervalSince1970]) * 1000];
|
||||
if (self->_updateSignatureSuccess != nil) {
|
||||
self->_updateSignatureSuccess();
|
||||
self->_updateSignatureSuccess = nil;
|
||||
}
|
||||
}
|
||||
fail:^(int code, NSString *desc) {
|
||||
NSLog(@"getVideoEditSignature: failed. code=%@, desc=%@", @(code), desc);
|
||||
if (code == ERR_SDK_INTERFACE_NOT_SUPPORT) {
|
||||
[self cancelTimer];
|
||||
return;
|
||||
}
|
||||
self.retryCount ++;
|
||||
if (self.retryCount > SCHEDULE_UPDATE_SIGNATURE_RETRY_TIMES) {
|
||||
self.retryCount = 0;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
NSLog(@"getVideoEditSignature: Attempting to get signature for the %ld time",self.retryCount);
|
||||
[self scheduleUpdateSignature:SCHEDULE_UPDATE_SIGNATURE_INTERVAL_WHEN_FAIL];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setSignatureToSDK {
|
||||
NSDictionary *param = @{
|
||||
@"api" : @"setSignature",
|
||||
@"params" : @{
|
||||
@"appid" : [@([TUILogin getSdkAppID]) stringValue],
|
||||
@"signature" : _signature,
|
||||
},
|
||||
};
|
||||
NSError *error = nil;
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:param options:0 error:&error];
|
||||
if (error != nil) {
|
||||
NSLog(@"TUIMultimedia GetMultimediaIsSupport Error:%@", error);
|
||||
}
|
||||
NSString *paramStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
NSLog(@"TUIMultimedia setSignature:%@", paramStr);
|
||||
|
||||
SEL selector = NSSelectorFromString(@"callExperimentalAPI:");
|
||||
if ([[TXUGCBase class] respondsToSelector:selector]) {
|
||||
// [TXUGCBase callExperimentalAPI:paramStr];
|
||||
[[TXUGCBase class] performSelector:selector withObject:paramStr];
|
||||
} else {
|
||||
NSLog(@"callExperimentalAPI: method is not available.");
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isFunctionSupport {
|
||||
return _signature != nil && _signature.length > 0 && [NSDate.now timeIntervalSince1970] < _expiredTime;
|
||||
}
|
||||
|
||||
-(void)scheduleUpdateSignature:(float)interval {
|
||||
[self cancelTimer];
|
||||
interval = interval / 1000;
|
||||
_timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(updateSignatureIfNeed) userInfo:nil repeats:NO];
|
||||
}
|
||||
|
||||
- (void)cancelTimer {
|
||||
if (_timer != nil) {
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaAuthorizationPrompter : UIViewController
|
||||
+ (BOOL) verifyPermissionGranted:(UIViewController*)parentView;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaAuthorizationPrompter.h"
|
||||
#import <SafariServices/SafariServices.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSignatureChecker.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
|
||||
#define IM_MULTIMEDIA_PLUGIN_DOCUMENT_URL @"https://cloud.tencent.com/document/product/269/113290"
|
||||
|
||||
@interface TUIMultimediaAuthorizationPrompter () {
|
||||
UIButton *_confirmButton;
|
||||
}
|
||||
|
||||
@property (nonatomic, copy) void (^dismissHandler)(void);
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaAuthorizationPrompter
|
||||
|
||||
+ (BOOL) verifyPermissionGranted:(UIViewController*)parentView {
|
||||
if ([[TUIMultimediaSignatureChecker shareInstance] isFunctionSupport]) {
|
||||
return YES;
|
||||
}
|
||||
NSLog(@"signature checker do not support function.");
|
||||
if (parentView != nil) {
|
||||
[TUIMultimediaAuthorizationPrompter showPrompterDialogInViewController:parentView];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (void)showPrompterDialogInViewController:(UIViewController *)presentingVC {
|
||||
TUIMultimediaAuthorizationPrompter *dialog = [[TUIMultimediaAuthorizationPrompter alloc] init];
|
||||
dialog.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
dialog.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
[presentingVC presentViewController:dialog animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
[self setupUI];
|
||||
}
|
||||
|
||||
- (void)setupUI {
|
||||
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
|
||||
|
||||
UIView *container = [[UIView alloc] init];
|
||||
container.backgroundColor = [UIColor tertiarySystemBackgroundColor];
|
||||
container.layer.cornerRadius = 16;
|
||||
container.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.view addSubview:container];
|
||||
|
||||
UIStackView *titleStack = [[UIStackView alloc] init];
|
||||
titleStack.axis = UILayoutConstraintAxisHorizontal;
|
||||
titleStack.spacing = 12;
|
||||
UILabel *titleLabel = [[UILabel alloc] init];
|
||||
titleLabel.text = [TUIMultimediaCommon localizedStringForKey:@"prompter"];
|
||||
titleLabel.font = [UIFont systemFontOfSize:20];
|
||||
titleLabel.textColor = [UIColor labelColor];
|
||||
[titleStack addArrangedSubview:titleLabel];
|
||||
|
||||
UILabel *prompter = [[UILabel alloc] init];
|
||||
prompter.numberOfLines = 0;
|
||||
prompter.text = [TUIMultimediaCommon localizedStringForKey:@"authorization_prompter"];
|
||||
prompter.font = [UIFont systemFontOfSize:14];
|
||||
prompter.textColor = [UIColor secondaryLabelColor];
|
||||
|
||||
UITextView *prompter_access_docments = [[UITextView alloc] init];
|
||||
prompter_access_docments.editable = NO;
|
||||
prompter_access_docments.scrollEnabled = NO;
|
||||
prompter_access_docments.backgroundColor = [UIColor clearColor];
|
||||
prompter_access_docments.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
|
||||
NSString* prompter_accessing_documents = [TUIMultimediaCommon localizedStringForKey:@"authorization_prompter_accessing_documents"];
|
||||
NSString* documents_title = [TUIMultimediaCommon localizedStringForKey:@"authorization_prompter_documents_title"];
|
||||
NSRange title_range = [prompter_accessing_documents rangeOfString:documents_title];
|
||||
|
||||
NSMutableAttributedString *linkText = [[NSMutableAttributedString alloc] initWithString:prompter_accessing_documents];
|
||||
[linkText addAttribute:NSLinkAttributeName
|
||||
value:IM_MULTIMEDIA_PLUGIN_DOCUMENT_URL
|
||||
range:title_range];
|
||||
prompter_access_docments.attributedText = linkText;
|
||||
prompter_access_docments.tintColor = [UIColor systemBlueColor];
|
||||
prompter_access_docments.font = [UIFont systemFontOfSize:14];
|
||||
prompter_access_docments.textColor = [UIColor secondaryLabelColor];
|
||||
|
||||
|
||||
UILabel *prompter_remove_module = [[UILabel alloc] init];
|
||||
prompter_remove_module.numberOfLines = 0;
|
||||
prompter_remove_module.text = [TUIMultimediaCommon localizedStringForKey:@"authorization_prompter_remove_module"];
|
||||
prompter_remove_module.font = [UIFont systemFontOfSize:14];
|
||||
prompter_remove_module.textColor = [UIColor secondaryLabelColor];
|
||||
|
||||
|
||||
_confirmButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[_confirmButton setTitle:[TUIMultimediaCommon localizedStringForKey:@"ok"] forState:UIControlStateNormal];
|
||||
[_confirmButton addTarget:self
|
||||
action:@selector(confirmAction)
|
||||
forControlEvents:UIControlEventTouchUpInside];
|
||||
_confirmButton.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
|
||||
|
||||
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[
|
||||
titleStack, prompter, prompter_access_docments, prompter_remove_module, _confirmButton
|
||||
]];
|
||||
stack.axis = UILayoutConstraintAxisVertical;
|
||||
stack.spacing = 6;
|
||||
stack.alignment = UIStackViewAlignmentLeading;
|
||||
stack.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[container addSubview:stack];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[container.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
|
||||
[container.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
|
||||
[container.widthAnchor constraintEqualToAnchor:self.view.widthAnchor
|
||||
multiplier:0.8],
|
||||
|
||||
[stack.topAnchor constraintEqualToAnchor:container.topAnchor constant:20],
|
||||
[stack.leadingAnchor constraintEqualToAnchor:container.leadingAnchor constant:20],
|
||||
[stack.trailingAnchor constraintEqualToAnchor:container.trailingAnchor constant:-20],
|
||||
[stack.bottomAnchor constraintEqualToAnchor:container.bottomAnchor constant:-20],
|
||||
|
||||
[_confirmButton.leadingAnchor constraintEqualToAnchor:stack.leadingAnchor]
|
||||
]];
|
||||
}
|
||||
|
||||
- (void)confirmAction {
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - UITextViewDelegate
|
||||
- (BOOL)textView:(UITextView *)textView
|
||||
shouldInteractWithURL:(NSURL *)URL
|
||||
inRange:(NSRange)characterRange
|
||||
interaction:(UITextItemInteraction)interaction {
|
||||
SFSafariViewController *safari = [[SFSafariViewController alloc] initWithURL:URL];
|
||||
[self presentViewController:safari animated:YES completion:nil];
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaAutoScrollLabel : UIView
|
||||
@property(nonatomic) CGFloat scrollSpeed;
|
||||
@property(nonatomic) NSTimeInterval pauseInterval;
|
||||
@property(nonatomic) NSAttributedString *text;
|
||||
@property(nonatomic) CGFloat fadeInOutRate;
|
||||
@property(nonatomic) BOOL active;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaAutoScrollLabel.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
|
||||
static NSString *const ScrollAnimeKey = @"ScrollAnimeKey";
|
||||
|
||||
@interface TUIMultimediaAutoScrollLabel () <CAAnimationDelegate> {
|
||||
UIScrollView *_scrollView;
|
||||
UILabel *_label;
|
||||
UILabel *_label2;
|
||||
CAGradientLayer *_fadeLayer;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaAutoScrollLabel
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_scrollSpeed = 20;
|
||||
_pauseInterval = 2;
|
||||
_text = [[NSAttributedString alloc] initWithString:@""];
|
||||
_fadeInOutRate = 0.1;
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
_scrollView = [[UIScrollView alloc] init];
|
||||
_scrollView.scrollEnabled = NO;
|
||||
_scrollView.userInteractionEnabled = NO;
|
||||
[self addSubview:_scrollView];
|
||||
|
||||
_label = [[UILabel alloc] init];
|
||||
_label.numberOfLines = 1;
|
||||
[_scrollView addSubview:_label];
|
||||
|
||||
_label2 = [[UILabel alloc] init];
|
||||
_label2.numberOfLines = 1;
|
||||
[_scrollView addSubview:_label2];
|
||||
|
||||
NSArray *fadeColorList = @[
|
||||
(__bridge id)[UIColor.blackColor colorWithAlphaComponent:0].CGColor,
|
||||
(__bridge id)UIColor.blackColor.CGColor,
|
||||
(__bridge id)UIColor.blackColor.CGColor,
|
||||
(__bridge id)[UIColor.blackColor colorWithAlphaComponent:0].CGColor,
|
||||
];
|
||||
_fadeLayer = [[CAGradientLayer alloc] init];
|
||||
_fadeLayer.backgroundColor = UIColor.clearColor.CGColor;
|
||||
_fadeLayer.colors = fadeColorList;
|
||||
_fadeLayer.locations = @[ @0, @(_fadeInOutRate), @(1 - _fadeInOutRate), @1 ];
|
||||
_fadeLayer.startPoint = CGPointMake(0, 0.5);
|
||||
_fadeLayer.endPoint = CGPointMake(1, 0.5);
|
||||
|
||||
[_scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.equalTo(_label.mas_height);
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
[_label mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.top.bottom.equalTo(_scrollView);
|
||||
}];
|
||||
[_label2 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.bottom.equalTo(_scrollView);
|
||||
make.left.equalTo(_label.mas_right).offset(self.bounds.size.width);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_fadeLayer.frame = self.bounds;
|
||||
if (_active) {
|
||||
[self startScroll];
|
||||
}
|
||||
[_label2 mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(_label.mas_right).offset(self.bounds.size.width);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)startScroll {
|
||||
CGFloat scrollWidth = _scrollView.bounds.size.width;
|
||||
CGFloat labelWidth = _label.bounds.size.width;
|
||||
|
||||
[_scrollView.layer removeAnimationForKey:ScrollAnimeKey];
|
||||
|
||||
_scrollView.bounds = CGRectMake(0, 0, _scrollView.bounds.size.width, _scrollView.bounds.size.height);
|
||||
|
||||
CABasicAnimation *anime = [CABasicAnimation animationWithKeyPath:@"bounds.origin.x"];
|
||||
anime.fromValue = @(0);
|
||||
anime.toValue = @(labelWidth + scrollWidth);
|
||||
anime.duration = (labelWidth + scrollWidth) / _scrollSpeed;
|
||||
anime.removedOnCompletion = NO;
|
||||
anime.delegate = self;
|
||||
[_scrollView.layer addAnimation:anime forKey:ScrollAnimeKey];
|
||||
}
|
||||
|
||||
- (void)stopScroll {
|
||||
[_scrollView.layer removeAnimationForKey:ScrollAnimeKey];
|
||||
_scrollView.contentOffset = CGPointMake(0, 0);
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)anime finished:(BOOL)finished {
|
||||
if (anime == [_scrollView.layer animationForKey:ScrollAnimeKey] && finished && _active) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_pauseInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (self->_active) {
|
||||
[self startScroll];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
#pragma mark - Properties
|
||||
- (void)setActive:(BOOL)active {
|
||||
if (!_active && active) {
|
||||
[self startScroll];
|
||||
self.layer.mask = _fadeLayer;
|
||||
} else if (_active && !active) {
|
||||
[self stopScroll];
|
||||
self.layer.mask = nil;
|
||||
}
|
||||
_active = active;
|
||||
}
|
||||
- (void)setText:(NSAttributedString *)text {
|
||||
_label.attributedText = text;
|
||||
[_label sizeToFit];
|
||||
|
||||
_label2.attributedText = text;
|
||||
[_label2 sizeToFit];
|
||||
|
||||
_scrollView.contentSize = _label.bounds.size;
|
||||
_scrollView.contentOffset = CGPointMake(0, 0);
|
||||
if (_active) {
|
||||
[self startScroll];
|
||||
}
|
||||
}
|
||||
- (void)setFadeInOutRate:(CGFloat)fadeInOutRate {
|
||||
_fadeInOutRate = MAX(0, MIN(1, fadeInOutRate));
|
||||
_fadeLayer.locations = @[ @0, @(_fadeInOutRate), @(1 - _fadeInOutRate), @1 ];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaCheckBox : UIControl
|
||||
@property(nonatomic) BOOL on;
|
||||
@property(nonatomic) NSString *text;
|
||||
@property(nonatomic) UIColor *textColor;
|
||||
@property(nonatomic) CGSize iconSize;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaCheckBox.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConfig.h"
|
||||
|
||||
@interface TUIMultimediaCheckBox () {
|
||||
UILabel *_label;
|
||||
UIImageView *_imgView;
|
||||
UITapGestureRecognizer *_tapRec;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaCheckBox
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
_iconSize = CGSizeMake(18, 18);
|
||||
UIImage *imgOff = [TUIMultimediaImageUtil imageFromImage:TUIMultimediaPluginBundleThemeImage(@"checkbox_off_img", @"checkbox_off") withTintColor:[[TUIMultimediaConfig sharedInstance] getThemeColor]];;
|
||||
UIImage *imgOn = [TUIMultimediaImageUtil imageFromImage:TUIMultimediaPluginBundleThemeImage(@"checkbox_on_img", @"checkbox_on") withTintColor:[[TUIMultimediaConfig sharedInstance] getThemeColor]];
|
||||
_imgView = [[UIImageView alloc] initWithImage:imgOff highlightedImage:imgOn];
|
||||
[self addSubview:_imgView];
|
||||
_imgView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
|
||||
_label = [[UILabel alloc] init];
|
||||
[self addSubview:_label];
|
||||
_label.textColor = UIColor.whiteColor;
|
||||
|
||||
[_imgView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.top.bottom.equalTo(self);
|
||||
make.size.mas_equalTo(self->_iconSize);
|
||||
}];
|
||||
[_label mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self);
|
||||
make.left.equalTo(_imgView.mas_right).inset(5);
|
||||
make.centerY.equalTo(_imgView);
|
||||
}];
|
||||
|
||||
_tapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)];
|
||||
[self addGestureRecognizer:_tapRec];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onTap {
|
||||
[self setOn:!_on];
|
||||
[self sendActionsForControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (void)setOn:(BOOL)on {
|
||||
_on = on;
|
||||
_imgView.highlighted = on;
|
||||
}
|
||||
|
||||
- (void)setIconSize:(CGSize)iconSize {
|
||||
_iconSize = iconSize;
|
||||
[_imgView mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(self->_iconSize);
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)text {
|
||||
return _label.text;
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text {
|
||||
_label.text = text;
|
||||
}
|
||||
|
||||
- (UIColor *)textColor {
|
||||
return _label.textColor;
|
||||
}
|
||||
|
||||
- (void)setTextColor:(UIColor *)textColor {
|
||||
_label.textColor = textColor;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
环形进度条
|
||||
*/
|
||||
@interface TUIMultimediaCircleProgressView : UIView
|
||||
@property(nonatomic) CGFloat progress; // 0.0 ~ 1.0
|
||||
@property(nonatomic) UIColor *progressColor; // 进度条颜色
|
||||
@property(nonatomic) UIColor *progressBgColor; // 进度条背景颜色
|
||||
@property(nonatomic) CGFloat width; // 圆环宽度
|
||||
@property(nonatomic) BOOL clockwise;
|
||||
@property(nonatomic) CGFloat startAngle;
|
||||
@property(nonatomic) CAShapeLayerLineCap lineCap;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame;
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaCircleProgressView.h"
|
||||
|
||||
@interface TUIMultimediaCircleProgressView () {
|
||||
CAShapeLayer *_backLayer; // 背景图层
|
||||
CAShapeLayer *_frontLayer; // 用来填充的图层
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaCircleProgressView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_clockwise = YES;
|
||||
_width = 5;
|
||||
_startAngle = -M_PI / 2;
|
||||
_progressBgColor = UIColor.grayColor;
|
||||
_progressColor = UIColor.greenColor;
|
||||
_lineCap = kCALineCapRound;
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)initUI {
|
||||
_backLayer = [CAShapeLayer layer];
|
||||
[self.layer addSublayer:_backLayer];
|
||||
_backLayer.fillColor = nil;
|
||||
_backLayer.strokeColor = _progressBgColor.CGColor;
|
||||
|
||||
_frontLayer = [CAShapeLayer layer];
|
||||
[self.layer addSublayer:_frontLayer];
|
||||
_frontLayer.fillColor = nil;
|
||||
_frontLayer.lineCap = _lineCap;
|
||||
_frontLayer.strokeColor = _progressColor.CGColor;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
CGFloat len = MIN(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds));
|
||||
CGPoint center = CGPointMake(CGRectGetWidth(self.bounds) / 2, CGRectGetHeight(self.bounds) / 2);
|
||||
UIBezierPath *backgroundBezierPath = [UIBezierPath bezierPathWithArcCenter:center
|
||||
radius:(len - _width) / 2.0
|
||||
startAngle:0
|
||||
endAngle:M_PI * 2
|
||||
clockwise:_clockwise];
|
||||
UIBezierPath *frontFillBezierPath = [UIBezierPath bezierPathWithArcCenter:center
|
||||
radius:(len - _width) / 2.0
|
||||
startAngle:_startAngle
|
||||
endAngle:_startAngle + (_clockwise ? 2 * M_PI : -2 * M_PI)
|
||||
clockwise:_clockwise];
|
||||
_backLayer.path = backgroundBezierPath.CGPath;
|
||||
_frontLayer.path = frontFillBezierPath.CGPath;
|
||||
_frontLayer.lineWidth = _width;
|
||||
_backLayer.lineWidth = _width;
|
||||
_frontLayer.strokeEnd = 0;
|
||||
}
|
||||
|
||||
#pragma mark - setters
|
||||
- (void)setProgress:(CGFloat)progress {
|
||||
_progress = MAX(0, MIN(1, progress));
|
||||
}
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
|
||||
_progress = MAX(0, MIN(1, progress));
|
||||
if (animated) {
|
||||
[UIView animateWithDuration:0.25
|
||||
animations:^{
|
||||
self->_frontLayer.strokeEnd = progress;
|
||||
}];
|
||||
} else {
|
||||
self->_frontLayer.strokeEnd = progress;
|
||||
}
|
||||
}
|
||||
- (void)setClockwise:(BOOL)clockwise {
|
||||
_clockwise = clockwise;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
- (void)setStartAngle:(CGFloat)startAngle {
|
||||
_startAngle = startAngle;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
- (void)setWidth:(CGFloat)width {
|
||||
_width = width;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
- (void)setProgressColor:(UIColor *)progressColor {
|
||||
_progressColor = progressColor;
|
||||
_frontLayer.strokeColor = _progressColor.CGColor;
|
||||
}
|
||||
- (void)setProgressBgColor:(UIColor *)progressBgColor {
|
||||
_progressBgColor = progressBgColor;
|
||||
_backLayer.strokeColor = _progressBgColor.CGColor;
|
||||
}
|
||||
- (void)setLineCap:(CAShapeLayerLineCap)lineCap {
|
||||
_lineCap = lineCap;
|
||||
_frontLayer.lineCap = lineCap;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaIconLabelButton : UIButton
|
||||
@property(nonatomic) CGSize iconSize;
|
||||
@property(nonatomic) CGFloat iconLabelGap;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaIconLabelButton.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface TUIMultimediaIconLabelButton () {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaIconLabelButton
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
return self;
|
||||
}
|
||||
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
|
||||
CGFloat midX = CGRectGetMidX(contentRect);
|
||||
CGFloat minY = CGRectGetMinY(contentRect);
|
||||
return CGRectMake(midX - _iconSize.width / 2, minY, _iconSize.width, _iconSize.height);
|
||||
}
|
||||
- (CGRect)titleRectForContentRect:(CGRect)contentRect {
|
||||
CGRect imageRect = [self imageRectForContentRect:contentRect];
|
||||
CGSize size = self.currentAttributedTitle.size;
|
||||
CGFloat midX = CGRectGetMidX(contentRect);
|
||||
return CGRectMake(midX - size.width / 2, CGRectGetMaxY(imageRect) + _iconLabelGap, size.width, size.height);
|
||||
}
|
||||
- (CGSize)intrinsicContentSize {
|
||||
CGSize titleSize = [self titleRectForContentRect:CGRectZero].size;
|
||||
return CGSizeMake(MAX(_iconSize.width, titleSize.width), _iconSize.height + _iconLabelGap + titleSize.height);
|
||||
}
|
||||
- (void)setIconSize:(CGSize)iconSize {
|
||||
_iconSize = iconSize;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
- (void)setIconLabelGap:(CGFloat)iconLabelGap {
|
||||
_iconLabelGap = iconLabelGap;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaLabelCell : UICollectionViewCell
|
||||
@property(nullable, nonatomic) NSAttributedString *attributedText;
|
||||
- (instancetype)initWithFrame:(CGRect)frame;
|
||||
|
||||
+ (NSString *)reuseIdentifier;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaLabelCell.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface TUIMultimediaLabelCell () {
|
||||
UILabel *_label;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaLabelCell
|
||||
@dynamic attributedText;
|
||||
|
||||
+ (NSString *)reuseIdentifier {
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_label = [[UILabel alloc] init];
|
||||
[self.contentView addSubview:_label];
|
||||
_label.textAlignment = NSTextAlignmentCenter;
|
||||
[_label mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attrText {
|
||||
return _label.attributedText;
|
||||
}
|
||||
|
||||
- (void)setAttributedText:(NSAttributedString *)attributedText {
|
||||
_label.attributedText = attributedText;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaPopupController : UIViewController
|
||||
@property(nullable, nonatomic) UIView *mainView;
|
||||
@property(nonatomic) float animeDuration;
|
||||
|
||||
- (BOOL)popupControllerWillCancel;
|
||||
- (void)popupControllerDidCanceled;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPopupController.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface TUIMultimediaPopupController () {
|
||||
UIView *_popupView;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPopupController
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_animeDuration = 0.15;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
_popupView = [[UIView alloc] init];
|
||||
[self.view addSubview:_popupView];
|
||||
|
||||
UIView *cancelView = [[UIView alloc] init];
|
||||
[self.view addSubview:cancelView];
|
||||
|
||||
[_popupView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self.view);
|
||||
make.top.equalTo(self.view.mas_bottom);
|
||||
}];
|
||||
[cancelView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.left.right.equalTo(self.view);
|
||||
make.bottom.equalTo(_popupView.mas_top);
|
||||
}];
|
||||
|
||||
UITapGestureRecognizer *rec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onCancelViewTap:)];
|
||||
rec.cancelsTouchesInView = NO;
|
||||
[cancelView addGestureRecognizer:rec];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[UIView animateWithDuration:_animeDuration
|
||||
delay:0
|
||||
options:UIViewAnimationOptionCurveLinear
|
||||
animations:^{
|
||||
[self->_popupView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self.view);
|
||||
make.bottom.equalTo(self.view.mas_bottom);
|
||||
}];
|
||||
[self.view layoutIfNeeded];
|
||||
}
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
- (BOOL)popupControllerWillCancel {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)popupControllerDidCanceled {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
- (void)onCancelViewTap:(UITapGestureRecognizer *)rec {
|
||||
BOOL b = [self popupControllerWillCancel];
|
||||
if (b) {
|
||||
[UIView animateWithDuration:_animeDuration
|
||||
delay:0
|
||||
options:UIViewAnimationOptionCurveLinear
|
||||
animations:^{
|
||||
[self->_popupView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self.view);
|
||||
make.top.equalTo(self.view.mas_bottom);
|
||||
}];
|
||||
[self.view layoutIfNeeded];
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[self popupControllerDidCanceled];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMainView:(UIView *)mainView {
|
||||
if (_mainView != nil) {
|
||||
[_mainView removeFromSuperview];
|
||||
}
|
||||
_mainView = mainView;
|
||||
[_popupView addSubview:_mainView];
|
||||
[mainView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self->_popupView);
|
||||
}];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaSplitter : UIView
|
||||
@property(nonatomic) UILayoutConstraintAxis axis;
|
||||
@property(nonatomic) UIColor *color;
|
||||
@property(nonatomic) CGFloat lineWidth;
|
||||
@property(nonatomic) CGLineCap lineCap;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSplitter.h"
|
||||
|
||||
@interface TUIMultimediaSplitter () {
|
||||
CAShapeLayer *_shapeLayer;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaSplitter
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
_color = UIColor.lightGrayColor;
|
||||
_lineWidth = 1;
|
||||
_lineCap = kCGLineCapRound;
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
|
||||
_shapeLayer = [[CAShapeLayer alloc] init];
|
||||
[self.layer addSublayer:_shapeLayer];
|
||||
|
||||
[self updateShapeLayer];
|
||||
return self;
|
||||
}
|
||||
- (void)updateShapeLayer {
|
||||
_shapeLayer.frame = self.bounds;
|
||||
CGPoint startPoint, endPoint;
|
||||
CGSize size = self.bounds.size;
|
||||
CGFloat inset = 0;
|
||||
if (_axis == UILayoutConstraintAxisVertical) {
|
||||
startPoint = CGPointMake(size.width / 2, inset);
|
||||
endPoint = CGPointMake(size.width / 2, size.height - inset);
|
||||
} else {
|
||||
startPoint = CGPointMake(inset, size.height / 2);
|
||||
endPoint = CGPointMake(size.width - inset, size.height / 2);
|
||||
}
|
||||
UIBezierPath *path = [UIBezierPath bezierPath];
|
||||
[path moveToPoint:startPoint];
|
||||
[path addLineToPoint:endPoint];
|
||||
_shapeLayer.path = path.CGPath;
|
||||
_shapeLayer.strokeColor = _color.CGColor;
|
||||
_shapeLayer.lineWidth = _lineWidth;
|
||||
switch (_lineCap) {
|
||||
default:
|
||||
case kCGLineCapButt:
|
||||
_shapeLayer.lineCap = kCALineCapButt;
|
||||
break;
|
||||
case kCGLineCapRound:
|
||||
_shapeLayer.lineCap = kCALineCapRound;
|
||||
break;
|
||||
case kCGLineCapSquare:
|
||||
_shapeLayer.lineCap = kCALineCapSquare;
|
||||
break;
|
||||
}
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[self updateShapeLayer];
|
||||
}
|
||||
- (void)setAxis:(UILayoutConstraintAxis)axis {
|
||||
_axis = axis;
|
||||
[self updateShapeLayer];
|
||||
}
|
||||
- (void)setColor:(UIColor *)color {
|
||||
_color = color;
|
||||
[self updateShapeLayer];
|
||||
}
|
||||
- (void)setLineWidth:(CGFloat)lineWidth {
|
||||
_lineWidth = lineWidth;
|
||||
[self updateShapeLayer];
|
||||
}
|
||||
- (void)setLineCap:(CGLineCap)lineCap {
|
||||
_lineCap = lineCap;
|
||||
[self updateShapeLayer];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMultimediaTabPanelTab;
|
||||
|
||||
@protocol TUIMultimediaTabPanelDelegate;
|
||||
|
||||
@interface TUIMultimediaTabPanel : UIView
|
||||
@property(nonatomic) NSArray<TUIMultimediaTabPanelTab *> *tabs;
|
||||
@property(nonatomic) NSInteger selectedIndex;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaTabPanelDelegate> delegate;
|
||||
- (id)initWithFrame:(CGRect)frame;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaTabPanelDelegate <NSObject>
|
||||
- (void)tabPanel:(TUIMultimediaTabPanel *)panel selectedIndexChanged:(NSInteger)selectedIndex;
|
||||
@end
|
||||
|
||||
@interface TUIMultimediaTabPanelTab : NSObject
|
||||
@property(nonatomic) UIView *view;
|
||||
@property(nullable, nonatomic) NSString *name;
|
||||
@property(nullable, nonatomic) UIImage *icon;
|
||||
- (instancetype)initWithName:(nullable NSString *)name icon:(nullable UIImage *)icon view:(UIView *)view;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaTabBarDelegate;
|
||||
|
||||
@interface TUIMultimediaTabBar : UIView
|
||||
@property(nonatomic) NSArray<id> *tabs;
|
||||
@property(nonatomic) NSInteger selectedIndex;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaTabBarDelegate> delegate;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaTabBarDelegate <NSObject>
|
||||
- (void)tabBar:(TUIMultimediaTabBar *)bar selectedIndexChanged:(NSInteger)index;
|
||||
@end
|
||||
|
||||
@interface TUIMultimediaTabBarCell : UICollectionViewCell
|
||||
@property(nullable, nonatomic) NSAttributedString *attributedText;
|
||||
@property(nullable, nonatomic) UIImage *icon;
|
||||
@property(nonatomic) CGFloat padding;
|
||||
@property(nonatomic) BOOL barCellSelected;
|
||||
+ (NSString *)reuseIdentifier;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
286
TUIKit/TUIMultimediaPlugin/Common/View/TUIMultimediaTabPanel.m
Normal file
286
TUIKit/TUIMultimediaPlugin/Common/View/TUIMultimediaTabPanel.m
Normal file
@@ -0,0 +1,286 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaTabPanel.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSplitter.h"
|
||||
|
||||
@interface TUIMultimediaTabPanel () <TUIMultimediaTabBarDelegate> {
|
||||
TUIMultimediaTabBar *_bar;
|
||||
TUIMultimediaSplitter *_splitter;
|
||||
NSArray<TUIMultimediaTabPanelTab *> *_tabs;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaTabPanel
|
||||
|
||||
@dynamic tabs;
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
[self initUI];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
_bar = [[TUIMultimediaTabBar alloc] init];
|
||||
[self addSubview:_bar];
|
||||
_bar.delegate = self;
|
||||
|
||||
_splitter = [[TUIMultimediaSplitter alloc] init];
|
||||
[self addSubview:_splitter];
|
||||
_splitter.lineWidth = 1;
|
||||
_splitter.color = TUIMultimediaPluginDynamicColor(@"tabpanel_splitter_color", @"#FFFFFF1A");
|
||||
|
||||
[_bar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.top.equalTo(self).inset(5);
|
||||
make.height.mas_equalTo(42);
|
||||
}];
|
||||
[_splitter mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self);
|
||||
make.top.equalTo(_bar.mas_bottom);
|
||||
make.height.mas_equalTo(5);
|
||||
}];
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (NSInteger)selectedIndex {
|
||||
return _bar.selectedIndex;
|
||||
}
|
||||
|
||||
- (void)setSelectedIndex:(NSInteger)selectedIndex {
|
||||
_bar.selectedIndex = selectedIndex;
|
||||
for (int i = 0; i < _tabs.count; i++) {
|
||||
TUIMultimediaTabPanelTab *t = _tabs[i];
|
||||
t.view.hidden = i != selectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<TUIMultimediaTabPanelTab *> *)tabs {
|
||||
return _tabs;
|
||||
}
|
||||
|
||||
- (void)setTabs:(NSArray<TUIMultimediaTabPanelTab *> *)value {
|
||||
if (_tabs != nil) {
|
||||
for (TUIMultimediaTabPanelTab *t in _tabs) {
|
||||
[t.view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
_tabs = value;
|
||||
_bar.selectedIndex = -1;
|
||||
_bar.tabs = [value tui_multimedia_map:^id(TUIMultimediaTabPanelTab *t) {
|
||||
return t.icon == nil ? t.name : t.icon;
|
||||
}];
|
||||
for (int i = 0; i < _tabs.count; i++) {
|
||||
TUIMultimediaTabPanelTab *t = _tabs[i];
|
||||
t.view.hidden = YES;
|
||||
[self addSubview:t.view];
|
||||
[t.view mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.bottom.equalTo(self);
|
||||
make.top.equalTo(_splitter.mas_bottom);
|
||||
}];
|
||||
}
|
||||
if (_tabs.count > 0) {
|
||||
TUIMultimediaTabPanelTab *t = _tabs[0];
|
||||
_bar.selectedIndex = 0;
|
||||
t.view.hidden = NO;
|
||||
}
|
||||
}
|
||||
#pragma mark - TUIMultimediaTabBarDelegate protocol
|
||||
- (void)tabBar:(TUIMultimediaTabBar *)bar selectedIndexChanged:(NSInteger)index {
|
||||
for (int i = 0; i < _tabs.count; i++) {
|
||||
TUIMultimediaTabPanelTab *t = _tabs[i];
|
||||
t.view.hidden = i != index;
|
||||
}
|
||||
[_delegate tabPanel:self selectedIndexChanged:index];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - TUIMultimediaTabPanelTab
|
||||
@implementation TUIMultimediaTabPanelTab {
|
||||
}
|
||||
- (instancetype)initWithName:(NSString *)name icon:(UIImage *)icon view:(UIView *)view {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_name = name;
|
||||
_icon = icon;
|
||||
_view = view;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
#pragma mark - TUIMultimediaTabBar
|
||||
|
||||
@interface TUIMultimediaTabBar () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> {
|
||||
UICollectionView *_collectionView;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaTabBar
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
[self initUI];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
layout.minimumInteritemSpacing = 10;
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
_collectionView.backgroundColor = UIColor.clearColor;
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
[_collectionView registerClass:TUIMultimediaTabBarCell.class forCellWithReuseIdentifier:TUIMultimediaTabBarCell.reuseIdentifier];
|
||||
[self addSubview:_collectionView];
|
||||
|
||||
[_collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setSelectedIndex:(NSInteger)selectedIndex {
|
||||
_selectedIndex = selectedIndex;
|
||||
[_collectionView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegateFlowLayout protocol
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[collectionView deselectItemAtIndexPath:indexPath animated:NO];
|
||||
_selectedIndex = indexPath.item;
|
||||
[collectionView reloadData];
|
||||
|
||||
[_delegate tabBar:self selectedIndexChanged:_selectedIndex];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource protocol
|
||||
- (NSAttributedString *)getAttributedString:(NSString *)str selected:(BOOL)selected {
|
||||
UIColor *color;
|
||||
UIFont *font;
|
||||
if (selected) {
|
||||
color = UIColor.whiteColor;
|
||||
font = [UIFont systemFontOfSize:16 weight:UIFontWeightBold];
|
||||
} else {
|
||||
color = [UIColor colorWithWhite:1 alpha:0.6];
|
||||
font = [UIFont systemFontOfSize:16 weight:UIFontWeightRegular];
|
||||
}
|
||||
NSAttributedString *text = [[NSAttributedString alloc] initWithString:str
|
||||
attributes:@{
|
||||
NSForegroundColorAttributeName : color,
|
||||
NSFontAttributeName : font,
|
||||
}];
|
||||
return text;
|
||||
}
|
||||
- (nonnull __kindof UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
|
||||
TUIMultimediaTabBarCell *cell = (TUIMultimediaTabBarCell *)[collectionView dequeueReusableCellWithReuseIdentifier:TUIMultimediaTabBarCell.reuseIdentifier
|
||||
forIndexPath:indexPath];
|
||||
cell.barCellSelected = indexPath.item == _selectedIndex;
|
||||
BOOL cellSelected = indexPath.item == _selectedIndex;
|
||||
id item = _tabs[indexPath.item];
|
||||
if ([item isKindOfClass:NSString.class]) {
|
||||
cell.attributedText = [self getAttributedString:item selected:cellSelected];
|
||||
} else if ([item isKindOfClass:UIImage.class]) {
|
||||
cell.contentView.backgroundColor = cellSelected ? [UIColor colorWithWhite:1 alpha:0.1] : UIColor.clearColor;
|
||||
cell.icon = item;
|
||||
cell.padding = 8;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
CGFloat h = _collectionView.bounds.size.height;
|
||||
CGSize size = CGSizeMake(h, h);
|
||||
id item = _tabs[indexPath.item];
|
||||
if ([item isKindOfClass:NSString.class]) {
|
||||
NSAttributedString *text = [self getAttributedString:_tabs[indexPath.item] selected:YES];
|
||||
CGSize textSize = [text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, _collectionView.bounds.size.height)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
context:nil]
|
||||
.size;
|
||||
size.width = MAX(size.width, textSize.width + 10);
|
||||
size.height = MAX(size.height, textSize.height + 8);
|
||||
return size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return _tabs.count;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - TUIMultimediaTabBarCell
|
||||
@interface TUIMultimediaTabBarCell () {
|
||||
UILabel *_label;
|
||||
UIImageView *_imgView;
|
||||
}
|
||||
@end
|
||||
@implementation TUIMultimediaTabBarCell
|
||||
+ (NSString *)reuseIdentifier {
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
self.contentView.layer.cornerRadius = 5;
|
||||
self.contentView.clipsToBounds = YES;
|
||||
|
||||
_label = [[UILabel alloc] init];
|
||||
[self.contentView addSubview:_label];
|
||||
_label.textAlignment = NSTextAlignmentCenter;
|
||||
[_label mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
_imgView = [[UIImageView alloc] init];
|
||||
[self.contentView addSubview:_imgView];
|
||||
_imgView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[_imgView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
self.attributedText = nil;
|
||||
self.icon = nil;
|
||||
self.barCellSelected = NO;
|
||||
self.padding = 0;
|
||||
}
|
||||
- (NSAttributedString *)attributedText {
|
||||
return _label.attributedText;
|
||||
}
|
||||
- (UIImage *)icon {
|
||||
return _imgView.image;
|
||||
}
|
||||
- (void)setAttributedText:(NSAttributedString *)attributedText {
|
||||
_label.attributedText = attributedText;
|
||||
}
|
||||
- (void)setIcon:(UIImage *)icon {
|
||||
_imgView.image = icon;
|
||||
}
|
||||
- (void)setPadding:(CGFloat)padding {
|
||||
_padding = padding;
|
||||
[_label mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self).inset(_padding);
|
||||
}];
|
||||
[_imgView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self).inset(_padding);
|
||||
}];
|
||||
}
|
||||
@end
|
||||
29
TUIKit/TUIMultimediaPlugin/Edit/Models/TUIMultimediaBGM.h
Normal file
29
TUIKit/TUIMultimediaPlugin/Edit/Models/TUIMultimediaBGM.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <AVKit/AVKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
BGM信息
|
||||
*/
|
||||
@interface TUIMultimediaBGM : NSObject <NSCopying>
|
||||
@property(nonatomic) NSString *name;
|
||||
@property(nullable, nonatomic) NSString *lyric; // 歌词
|
||||
@property(nullable, nonatomic) NSString *source; // 音乐来源或歌手
|
||||
@property(nonatomic) float startTime;
|
||||
@property(nonatomic) float endTime;
|
||||
@property(nullable, nonatomic) AVAsset *asset;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name lyric:(nullable NSString *)lyric source:(nullable NSString *)source asset:(nullable AVAsset *)asset;
|
||||
@end
|
||||
|
||||
@interface TUIMultimediaBGMGroup : NSObject
|
||||
@property(nonatomic) NSString *name;
|
||||
@property(nonatomic) NSArray<TUIMultimediaBGM *> *bgmList;
|
||||
- (instancetype)initWithName:(NSString *)name bgmList:(NSArray<TUIMultimediaBGM *> *)bgmList;
|
||||
+ (NSArray<TUIMultimediaBGMGroup *> *)loadBGMConfigs;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
86
TUIKit/TUIMultimediaPlugin/Edit/Models/TUIMultimediaBGM.m
Normal file
86
TUIKit/TUIMultimediaPlugin/Edit/Models/TUIMultimediaBGM.m
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaBGM.h"
|
||||
#import <TUICore/NSDictionary+TUISafe.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConfig.h"
|
||||
|
||||
@implementation TUIMultimediaBGM
|
||||
- (instancetype)initWithName:(NSString *)name lyric:(NSString *)lyric source:(NSString *)source asset:(nullable AVAsset *)asset {
|
||||
self = [super init];
|
||||
_name = name;
|
||||
_lyric = lyric;
|
||||
_source = source;
|
||||
_asset = asset;
|
||||
_startTime = 0;
|
||||
_endTime = 0;
|
||||
if (asset != nil && asset.duration.timescale != 0) {
|
||||
_endTime = asset.duration.value / asset.duration.timescale;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
TUIMultimediaBGM *cp = [[[self class] allocWithZone:zone] init];
|
||||
cp->_name = _name;
|
||||
cp->_lyric = _lyric;
|
||||
cp->_source = _source;
|
||||
cp->_asset = _asset;
|
||||
cp->_startTime = _startTime;
|
||||
cp->_endTime = _endTime;
|
||||
return cp;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaBGMGroup
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name bgmList:(NSArray<TUIMultimediaBGM *> *)bgmList {
|
||||
self = [super init];
|
||||
_name = name;
|
||||
_bgmList = bgmList;
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (TUIMultimediaBGMGroup *)loadGroupFromJsonDict:(NSDictionary *)dict {
|
||||
NSString *name = dict[@"bgm_type_name"];
|
||||
NSArray *jsonMusicList = dict[@"bgm_item_list"];
|
||||
NSMutableArray<TUIMultimediaBGM *> *bgmList = [NSMutableArray array];
|
||||
for (NSDictionary *music in jsonMusicList) {
|
||||
NSString *musicFile = music[@"item_bgm_path"];
|
||||
NSURL *url = [TUIMultimediaCommon getURLByResourcePath:musicFile];
|
||||
|
||||
AVAsset *asset = [AVAsset assetWithURL:url];
|
||||
TUIMultimediaBGM *bgm = [[TUIMultimediaBGM alloc] initWithName:music[@"item_bgm_name"] lyric:@"" source:music[@"item_bgm_author"] asset:asset];
|
||||
double startTime = [(NSNumber *)music[@"item_start_time"] doubleValue];
|
||||
double endTime = [(NSNumber *)music[@"item_end_time"] doubleValue];
|
||||
if (startTime >= 0 && endTime > 0 && endTime > startTime) {
|
||||
bgm.startTime = MIN(bgm.endTime, startTime);
|
||||
bgm.endTime = MIN(bgm.endTime, endTime);
|
||||
}
|
||||
if (asset.duration.value != 0) {
|
||||
[bgmList addObject:bgm];
|
||||
}
|
||||
}
|
||||
return [[TUIMultimediaBGMGroup alloc] initWithName:name bgmList:bgmList];
|
||||
}
|
||||
|
||||
+ (NSArray<TUIMultimediaBGMGroup *> *)loadBGMConfigs {
|
||||
NSData *jsonData = [NSData dataWithContentsOfFile:[TUIMultimediaCommon.bundle pathForResource:[[TUIMultimediaConfig sharedInstance] getBGMConfigFilePath] ofType:@"json"]];
|
||||
NSError *err = nil;
|
||||
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
|
||||
if (err || ![dic isKindOfClass:[NSDictionary class]]) {
|
||||
NSLog(@"[TUIMultimedia] Json parse failed: %@", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<TUIMultimediaBGMGroup *> *groupList = [NSMutableArray array];
|
||||
NSArray *jsonGroupList = dic[@"bgm_list"];
|
||||
for (NSDictionary *jsonGroup in jsonGroupList) {
|
||||
[groupList addObject:[self loadGroupFromJsonDict:jsonGroup]];
|
||||
}
|
||||
return groupList;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMultimediaPasterGroupConfig;
|
||||
@class TUIMultimediaPasterItemConfig;
|
||||
|
||||
/**
|
||||
贴纸配置
|
||||
*/
|
||||
@interface TUIMultimediaPasterConfig : NSObject
|
||||
@property(nonatomic) NSArray<TUIMultimediaPasterGroupConfig *> *groups;
|
||||
|
||||
+ (TUIMultimediaPasterConfig *)loadConfig;
|
||||
+ (void)saveConfig:(TUIMultimediaPasterConfig *)config;
|
||||
+ (NSURL *)saveCustomPaster:(UIImage *)img;
|
||||
+ (void)removeCustomPaster:(TUIMultimediaPasterItemConfig *)paster;
|
||||
@end
|
||||
|
||||
/**
|
||||
贴纸组,包含一系列贴纸
|
||||
*/
|
||||
@interface TUIMultimediaPasterGroupConfig : NSObject <NSSecureCoding>
|
||||
@property(nonatomic) NSString *name;
|
||||
@property(nullable, nonatomic) NSURL *iconUrl;
|
||||
@property(nonatomic) BOOL customizable; // 是否可由用户添加贴纸到此组
|
||||
@property(nonatomic) NSArray<TUIMultimediaPasterItemConfig *> *itemList;
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
iconUrl:(nullable NSURL *)iconUrl
|
||||
itemList:(NSArray<TUIMultimediaPasterItemConfig *> *)itemList
|
||||
customizable:(BOOL)customizable;
|
||||
- (UIImage *)loadIcon;
|
||||
@end
|
||||
|
||||
@interface TUIMultimediaPasterItemConfig : NSObject <NSSecureCoding>
|
||||
@property(nullable, nonatomic) NSURL *imageUrl;
|
||||
@property(nullable, nonatomic) NSURL *iconUrl;
|
||||
@property(nonatomic) BOOL isUserAdded;
|
||||
@property(nonatomic) BOOL isAddButton;
|
||||
|
||||
- (UIImage *)loadImage;
|
||||
- (UIImage *)loadIcon;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPasterConfig.h"
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPersistence.h"
|
||||
|
||||
static NSString *const FileCustomPasterConfig = @"liteav_multimedia_paster_config";
|
||||
static NSString *const PathCustomPasters = @"liteav_multimedia_pasters";
|
||||
|
||||
@implementation TUIMultimediaPasterConfig
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_groups = @[];
|
||||
return self;
|
||||
}
|
||||
+ (TUIMultimediaPasterItemConfig *)loadItemFromJsonDict:(NSDictionary *)groupData {
|
||||
NSString *iconPath = groupData[@"item_icon_path"];
|
||||
NSString *imagePath = groupData[@"item_image_path"];
|
||||
NSNumber *isAddButton = groupData[@"item_is_file_selector"];
|
||||
TUIMultimediaPasterItemConfig *item = [[TUIMultimediaPasterItemConfig alloc] init];
|
||||
item.iconUrl = [TUIMultimediaCommon getURLByResourcePath:iconPath];
|
||||
item.imageUrl = [TUIMultimediaCommon getURLByResourcePath:imagePath];
|
||||
item.isAddButton = isAddButton != nil && [isAddButton boolValue];
|
||||
item.isUserAdded = NO;
|
||||
return item;
|
||||
}
|
||||
+ (TUIMultimediaPasterGroupConfig *)loadGroupFromJsonDict:(NSDictionary *)groupData {
|
||||
NSString *name = groupData[@"type_name"];
|
||||
NSString *iconPath = groupData[@"type_icon_path"];
|
||||
NSURL *iconUrl = [TUIMultimediaCommon getURLByResourcePath:iconPath];
|
||||
BOOL customizable = NO;
|
||||
NSMutableArray<TUIMultimediaPasterItemConfig *> *pasterList = [NSMutableArray array];
|
||||
NSArray *itemDataList = groupData[@"paster_item_list"];
|
||||
for (NSDictionary *itemData in itemDataList) {
|
||||
TUIMultimediaPasterItemConfig *item = [self loadItemFromJsonDict:itemData];
|
||||
if (item.isAddButton) {
|
||||
customizable = YES;
|
||||
}
|
||||
[pasterList addObject:item];
|
||||
}
|
||||
return [[TUIMultimediaPasterGroupConfig alloc] initWithName:name iconUrl:iconUrl itemList:pasterList customizable:customizable];
|
||||
}
|
||||
+ (void)loadCustomPasterTo:(TUIMultimediaPasterConfig *)config {
|
||||
NSError *err;
|
||||
NSString *path = [TUIMultimediaPersistence.basePath stringByAppendingPathComponent:FileCustomPasterConfig];
|
||||
NSData *data = [TUIMultimediaPersistence loadDataFromFile:path error:&err];
|
||||
NSMutableArray<NSMutableArray<TUIMultimediaPasterItemConfig *> *> *saveList =
|
||||
[NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[ NSArray.class, TUIMultimediaPasterItemConfig.class ]] fromData:data error:&err];
|
||||
for (int i = 0; i < config.groups.count; i++) {
|
||||
NSMutableArray<TUIMultimediaPasterItemConfig *> *itemList = [NSMutableArray arrayWithArray:config.groups[i].itemList];
|
||||
[itemList addObjectsFromArray:saveList[i]];
|
||||
config.groups[i].itemList = itemList;
|
||||
}
|
||||
}
|
||||
+ (TUIMultimediaPasterConfig *)loadConfig {
|
||||
NSData *jsonData = [NSData dataWithContentsOfFile:[TUIMultimediaCommon.bundle pathForResource:[[TUIMultimediaConfig sharedInstance] getPicturePasterConfigFilePath] ofType:@"json"]];
|
||||
NSError *err = nil;
|
||||
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&err];
|
||||
if (err || ![dic isKindOfClass:[NSDictionary class]]) {
|
||||
NSLog(@"[TUIMultimedia] Json parse failed: %@", err);
|
||||
return nil;
|
||||
}
|
||||
NSArray *groupListData = dic[@"paster_type_list"];
|
||||
NSMutableArray<TUIMultimediaPasterGroupConfig *> *groups = [NSMutableArray array];
|
||||
for (NSDictionary *jsonGroup in groupListData) {
|
||||
[groups addObject:[self loadGroupFromJsonDict:jsonGroup]];
|
||||
}
|
||||
TUIMultimediaPasterConfig *config = [[TUIMultimediaPasterConfig alloc] init];
|
||||
config.groups = groups;
|
||||
[self loadCustomPasterTo:config];
|
||||
return config;
|
||||
}
|
||||
|
||||
+ (void)saveConfig:(TUIMultimediaPasterConfig *)config {
|
||||
NSMutableArray<NSMutableArray<TUIMultimediaPasterItemConfig *> *> *saveList = [NSMutableArray array];
|
||||
for (TUIMultimediaPasterGroupConfig *g in config.groups) {
|
||||
NSMutableArray<TUIMultimediaPasterItemConfig *> *list = [NSMutableArray array];
|
||||
[saveList addObject:list];
|
||||
if (g.customizable) {
|
||||
for (TUIMultimediaPasterItemConfig *item in g.itemList) {
|
||||
if (item.isUserAdded) {
|
||||
[list addObject:item];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NSError *err;
|
||||
NSString *path = [TUIMultimediaPersistence.basePath stringByAppendingPathComponent:FileCustomPasterConfig];
|
||||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:saveList requiringSecureCoding:YES error:&err];
|
||||
[TUIMultimediaPersistence saveData:data toFile:path error:nil];
|
||||
}
|
||||
|
||||
+ (NSURL *)saveCustomPaster:(UIImage *)img {
|
||||
NSString *pastersDir = [TUIMultimediaPersistence.basePath stringByAppendingPathComponent:PathCustomPasters];
|
||||
NSString *file = [pastersDir stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
|
||||
[TUIMultimediaPersistence saveData:UIImagePNGRepresentation(img) toFile:file error:nil];
|
||||
return [NSURL fileURLWithPath:file];
|
||||
}
|
||||
|
||||
+ (void)removeCustomPaster:(TUIMultimediaPasterItemConfig *)paster {
|
||||
if (!paster.isUserAdded) {
|
||||
return;
|
||||
}
|
||||
if (paster.iconUrl != nil && ![paster.iconUrl.absoluteString containsString:TUIMultimediaCommon.bundle.resourceURL.absoluteString]) {
|
||||
[NSFileManager.defaultManager removeItemAtURL:paster.iconUrl error:nil];
|
||||
}
|
||||
if (paster.imageUrl != nil && ![paster.imageUrl.absoluteString containsString:TUIMultimediaCommon.bundle.resourceURL.absoluteString]) {
|
||||
[NSFileManager.defaultManager removeItemAtURL:paster.imageUrl error:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIMultimediaPasterGroupConfig () {
|
||||
UIImage *_cachedIcon;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPasterGroupConfig
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
iconUrl:(nullable NSURL *)iconUrl
|
||||
itemList:(NSArray<TUIMultimediaPasterItemConfig *> *)itemList
|
||||
customizable:(BOOL)customizable {
|
||||
self = [super init];
|
||||
_name = name;
|
||||
_iconUrl = iconUrl;
|
||||
_itemList = itemList;
|
||||
_customizable = customizable;
|
||||
return self;
|
||||
}
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[coder encodeObject:_name forKey:@"name"];
|
||||
[coder encodeObject:_iconUrl forKey:@"iconUrl"];
|
||||
[coder encodeObject:_itemList forKey:@"itemList"];
|
||||
[coder encodeBool:_customizable forKey:@"customizable"];
|
||||
}
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super init];
|
||||
_name = [coder decodeObjectOfClass:NSString.class forKey:@"name"];
|
||||
_iconUrl = [coder decodeObjectOfClass:NSURL.class forKey:@"iconUrl"];
|
||||
_itemList = [coder decodeObjectOfClasses:[NSSet setWithObjects:NSArray.class, TUIMultimediaPasterItemConfig.class, nil] forKey:@"itemList"];
|
||||
_customizable = [coder decodeBoolForKey:@"customizable"];
|
||||
return self;
|
||||
}
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
- (UIImage *)loadIcon {
|
||||
if (_cachedIcon != nil) {
|
||||
return _cachedIcon;
|
||||
}
|
||||
_cachedIcon = [UIImage imageWithData:[NSData dataWithContentsOfURL:_iconUrl]];
|
||||
return _cachedIcon;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface TUIMultimediaPasterItemConfig () {
|
||||
UIImage *_cachedImage;
|
||||
UIImage *_cachedIcon;
|
||||
}
|
||||
@end
|
||||
@implementation TUIMultimediaPasterItemConfig
|
||||
|
||||
- (UIImage *)loadImage {
|
||||
if (_cachedImage != nil) {
|
||||
return _cachedImage;
|
||||
}
|
||||
_cachedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:_imageUrl]];
|
||||
return _cachedImage;
|
||||
}
|
||||
- (UIImage *)loadIcon {
|
||||
if (_iconUrl == nil) {
|
||||
return [self loadImage];
|
||||
}
|
||||
if (_cachedIcon != nil) {
|
||||
return _cachedIcon;
|
||||
}
|
||||
_cachedIcon = [UIImage imageWithData:[NSData dataWithContentsOfURL:_iconUrl]];
|
||||
return _cachedIcon;
|
||||
}
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[coder encodeObject:_imageUrl forKey:@"imageUrl"];
|
||||
[coder encodeObject:_iconUrl forKey:@"iconUrl"];
|
||||
[coder encodeBool:_isUserAdded forKey:@"isUserAdded"];
|
||||
[coder encodeBool:_isAddButton forKey:@"isAddButton"];
|
||||
}
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super init];
|
||||
_imageUrl = [coder decodeObjectOfClass:NSURL.class forKey:@"imageUrl"];
|
||||
_iconUrl = [coder decodeObjectOfClass:NSURL.class forKey:@"iconUrl"];
|
||||
_isUserAdded = [coder decodeBoolForKey:@"isUserAdded"];
|
||||
_isAddButton = [coder decodeBoolForKey:@"isAddButton"];
|
||||
return self;
|
||||
}
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
贴纸或字幕
|
||||
*/
|
||||
@interface TUIMultimediaSticker : NSObject
|
||||
@property(nullable, nonatomic) UIImage *image;
|
||||
@property(nonatomic) CGRect frame;
|
||||
- (instancetype)init;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSticker.h"
|
||||
|
||||
@implementation TUIMultimediaSticker
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
字幕信息
|
||||
*/
|
||||
@interface TUIMultimediaSubtitleInfo : NSObject <NSCopying>
|
||||
@property(nonatomic) NSString *text;
|
||||
@property(nonatomic) UIColor *color;
|
||||
@property(nonatomic) NSString *wrappedText;
|
||||
|
||||
- (instancetype)initWithText:(NSString *)text color:(UIColor *)color;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSubtitleInfo.h"
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
|
||||
@implementation TUIMultimediaSubtitleInfo
|
||||
- (instancetype)init {
|
||||
return [self initWithText:@"" color:UIColor.whiteColor];
|
||||
}
|
||||
- (instancetype)initWithText:(NSString *)text color:(UIColor *)color {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_text = text;
|
||||
_color = color;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (id)copyWithZone:(nullable NSZone *)zone {
|
||||
TUIMultimediaSubtitleInfo *cp = [[[self class] allocWithZone:zone] init];
|
||||
cp.text = _text;
|
||||
cp.color = _color;
|
||||
return cp;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "TUIMultimediaBGM.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaVideoBgmEditInfo : NSObject
|
||||
@property(nonatomic) BOOL originAudio;
|
||||
@property(nullable, nonatomic) TUIMultimediaBGM *bgm;
|
||||
- (instancetype)init;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaVideoBgmEditInfo.h"
|
||||
|
||||
@implementation TUIMultimediaVideoBgmEditInfo
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
20
TUIKit/TUIMultimediaPlugin/Edit/TUIMultimediaEncodeConfig.h
Normal file
20
TUIKit/TUIMultimediaPlugin/Edit/TUIMultimediaEncodeConfig.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <TXLiteAVSDK_Professional/TXUGCRecord.h>
|
||||
#import <TXLiteAVSDK_Professional/TXVideoEditer.h>
|
||||
#import <TXLiteAVSDK_Professional/TXVideoEditerTypeDef.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaEncodeConfig : NSObject
|
||||
@property(nonatomic) int fps;
|
||||
@property(nonatomic) int bitrate;
|
||||
|
||||
- (instancetype)initWithVideoQuality:(int)videoQuality;
|
||||
- (TXVideoCompressed)getVideoEditCompressed;
|
||||
- (TXVideoResolution)getVideoRecordResolution;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
62
TUIKit/TUIMultimediaPlugin/Edit/TUIMultimediaEncodeConfig.m
Normal file
62
TUIKit/TUIMultimediaPlugin/Edit/TUIMultimediaEncodeConfig.m
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaEncodeConfig.h"
|
||||
#import <TUICore/TUIDefine.h>
|
||||
|
||||
#define TUICore_TUIMultimediaService_VideoQuality_Low 1
|
||||
#define TUICore_TUIMultimediaService_VideoQuality_Medium 2
|
||||
#define TUICore_TUIMultimediaService_VideoQuality_High 3
|
||||
|
||||
@interface TUIMultimediaEncodeConfig () {
|
||||
int _videoQuality;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaEncodeConfig
|
||||
- (instancetype)init {
|
||||
return [self initWithVideoQuality:TUICore_TUIMultimediaService_VideoQuality_Medium];
|
||||
}
|
||||
|
||||
- (instancetype)initWithVideoQuality:(int)videoQuality {
|
||||
switch (videoQuality) {
|
||||
case TUICore_TUIMultimediaService_VideoQuality_Low:
|
||||
_bitrate = 1000;
|
||||
_fps = 25;
|
||||
break;
|
||||
case TUICore_TUIMultimediaService_VideoQuality_High:
|
||||
_bitrate = 5000;
|
||||
_fps = 30;
|
||||
break;
|
||||
case TUICore_TUIMultimediaService_VideoQuality_Medium:
|
||||
default:
|
||||
_bitrate = 3000;
|
||||
_fps = 25;
|
||||
break;
|
||||
}
|
||||
_videoQuality = videoQuality;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (TXVideoCompressed)getVideoEditCompressed {
|
||||
switch (_videoQuality) {
|
||||
case TUICore_TUIMultimediaService_VideoQuality_High:
|
||||
return VIDEO_COMPRESSED_1080P;
|
||||
case TUICore_TUIMultimediaService_VideoQuality_Low:
|
||||
case TUICore_TUIMultimediaService_VideoQuality_Medium:
|
||||
default:
|
||||
return VIDEO_COMPRESSED_720P;
|
||||
}
|
||||
}
|
||||
|
||||
- (TXVideoResolution)getVideoRecordResolution {
|
||||
switch (_videoQuality) {
|
||||
case TUICore_TUIMultimediaService_VideoQuality_High:
|
||||
return VIDEO_RESOLUTION_1080_1920;
|
||||
case TUICore_TUIMultimediaService_VideoQuality_Low:
|
||||
case TUICore_TUIMultimediaService_VideoQuality_Medium:
|
||||
default:
|
||||
return VIDEO_RESOLUTION_720_1280;
|
||||
}
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaFakeAudioWaveView : UIView
|
||||
@property(nonatomic) UIColor *color;
|
||||
@property(nonatomic) CGFloat lineWidth;
|
||||
@property(nonatomic) CGFloat lineSpacing;
|
||||
@property(nonatomic) BOOL enabled;
|
||||
@property(nonatomic) CGFloat animeInterval;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaFakeAudioWaveView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
|
||||
@interface TUIMultimediaFakeAudioWaveView () {
|
||||
dispatch_source_t _timer;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaFakeAudioWaveView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
_color = UIColor.grayColor;
|
||||
_lineWidth = 1.5;
|
||||
_lineSpacing = 0.5;
|
||||
_animeInterval = 0.2;
|
||||
_enabled = NO;
|
||||
|
||||
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
|
||||
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, _animeInterval * NSEC_PER_SEC, _animeInterval * NSEC_PER_SEC);
|
||||
dispatch_source_set_event_handler(_timer, ^{
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
dispatch_activate(_timer);
|
||||
dispatch_suspend(_timer);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
[super drawRect:rect];
|
||||
[_color set];
|
||||
CGFloat x = 0;
|
||||
CGFloat selfWidth = self.bounds.size.width;
|
||||
CGFloat selfHeight = self.bounds.size.height;
|
||||
while (x + _lineWidth < selfWidth) {
|
||||
CGFloat h = (CGFloat)arc4random() / UINT32_MAX * selfHeight;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, selfHeight - h, _lineWidth, selfHeight)];
|
||||
[path fill];
|
||||
x += _lineWidth + _lineSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (void)setColor:(UIColor *)color {
|
||||
_color = color;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
if (_enabled && !enabled) {
|
||||
dispatch_suspend(_timer);
|
||||
} else if (!_enabled && enabled) {
|
||||
dispatch_resume(_timer);
|
||||
}
|
||||
_enabled = enabled;
|
||||
}
|
||||
|
||||
- (void)setAnimeInterval:(CGFloat)animeInterval {
|
||||
_animeInterval = animeInterval;
|
||||
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, _animeInterval * NSEC_PER_SEC, _animeInterval * NSEC_PER_SEC);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPopupController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaPasterSelectControllerDelegate;
|
||||
|
||||
/**
|
||||
贴纸选择界面Controller
|
||||
*/
|
||||
@interface TUIMultimediaPasterSelectController : TUIMultimediaPopupController
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaPasterSelectControllerDelegate> delegate;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaPasterSelectControllerDelegate <NSObject>
|
||||
- (void)pasterSelectController:(TUIMultimediaPasterSelectController *)c onPasterSelected:(UIImage *)image;
|
||||
- (void)onPasterSelectControllerExit:(TUIMultimediaPasterSelectController *)c;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPasterSelectController.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <ReactiveObjC/RACEXTScope.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImagePicker.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterSelectView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPersistence.h"
|
||||
|
||||
@interface TUIMultimediaPasterSelectController () <TUIMultimediaPasterSelectViewDelegate> {
|
||||
TUIMultimediaPasterSelectView *_selectView;
|
||||
TUIMultimediaImagePicker *_picker;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPasterSelectController
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
_selectView = [[TUIMultimediaPasterSelectView alloc] init];
|
||||
_selectView.config = [TUIMultimediaPasterConfig loadConfig];
|
||||
_selectView.delegate = self;
|
||||
self.mainView = _selectView;
|
||||
}
|
||||
|
||||
- (void)popupControllerDidCanceled {
|
||||
[_delegate onPasterSelectControllerExit:self];
|
||||
}
|
||||
|
||||
- (void)onPasterSelected:(UIImage *)image {
|
||||
[_delegate pasterSelectController:self onPasterSelected:image];
|
||||
}
|
||||
|
||||
- (void)pasterSelectView:(TUIMultimediaPasterSelectView *)v needAddCustomPaster:(TUIMultimediaPasterGroupConfig *)group completeCallback:(void (^)(void))callback {
|
||||
_picker = [[TUIMultimediaImagePicker alloc] init];
|
||||
@weakify(self);
|
||||
_picker.callback = ^(UIImage *img) {
|
||||
@strongify(self);
|
||||
if (img == nil || self == nil) {
|
||||
return;
|
||||
}
|
||||
NSURL *url = [TUIMultimediaPasterConfig saveCustomPaster:img];
|
||||
if (url == nil) {
|
||||
return;
|
||||
}
|
||||
TUIMultimediaPasterItemConfig *item = [[TUIMultimediaPasterItemConfig alloc] init];
|
||||
item.imageUrl = url;
|
||||
item.isUserAdded = YES;
|
||||
group.itemList = [group.itemList arrayByAddingObject:item];
|
||||
[TUIMultimediaPasterConfig saveConfig:v.config];
|
||||
callback();
|
||||
};
|
||||
[_picker presentOn:self];
|
||||
}
|
||||
|
||||
- (void)pasterSelectView:(TUIMultimediaPasterSelectView *)v
|
||||
needDeleteCustomPasterInGroup:(TUIMultimediaPasterGroupConfig *)group
|
||||
index:(NSInteger)index
|
||||
completeCallback:(void (^)(BOOL deleted))callback {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:[TUIMultimediaCommon localizedStringForKey:@"remove_paster"]
|
||||
message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[TUIMultimediaCommon localizedStringForKey:@"cancel"]
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction *action) {
|
||||
callback(NO);
|
||||
}];
|
||||
UIAlertAction *deleteAction = [UIAlertAction actionWithTitle:[TUIMultimediaCommon localizedStringForKey:@"delete"]
|
||||
style:UIAlertActionStyleDestructive
|
||||
handler:^(UIAlertAction *action) {
|
||||
NSMutableArray<TUIMultimediaPasterItemConfig *> *list = [NSMutableArray arrayWithArray:group.itemList];
|
||||
TUIMultimediaPasterItemConfig *paster = list[index];
|
||||
[list removeObjectAtIndex:index];
|
||||
group.itemList = list;
|
||||
[TUIMultimediaPasterConfig removeCustomPaster:paster];
|
||||
[TUIMultimediaPasterConfig saveConfig:self->_selectView.config];
|
||||
callback(YES);
|
||||
}];
|
||||
[alert addAction:cancelAction];
|
||||
[alert addAction:deleteAction];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleInfo.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMultimediaSubtitleEditController;
|
||||
|
||||
typedef void (^TUIMultimediaSubtitleEditControllerCallback)(TUIMultimediaSubtitleEditController *c, BOOL isOk);
|
||||
|
||||
/**
|
||||
字幕编辑界面Controller
|
||||
*/
|
||||
@interface TUIMultimediaSubtitleEditController : UIViewController
|
||||
@property(copy, nonatomic) TUIMultimediaSubtitleInfo *subtitleInfo;
|
||||
@property(nullable, nonatomic) TUIMultimediaSubtitleEditControllerCallback callback;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSubtitleEditController.h"
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleEditView.h"
|
||||
|
||||
@interface TUIMultimediaSubtitleEditController () <TUIMultimediaSubtitleEditViewDelegate> {
|
||||
TUIMultimediaSubtitleEditView *_editView;
|
||||
BOOL _hasCallback;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaSubtitleEditController
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
_editView = [[TUIMultimediaSubtitleEditView alloc] initWithFrame:self.view.bounds];
|
||||
_editView.subtitleInfo = _subtitleInfo;
|
||||
_editView.delegate = self;
|
||||
[self.view addSubview:_editView];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[_editView activate];
|
||||
_hasCallback = false;
|
||||
}
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
_editView.frame = self.view.bounds;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaSubtitleEditViewDelegate protocol
|
||||
- (void)subtitleEditViewOnOk:(TUIMultimediaSubtitleEditView *)view {
|
||||
if (_callback != nil && !_hasCallback) {
|
||||
_hasCallback = true;
|
||||
_callback(self, YES);
|
||||
}
|
||||
}
|
||||
- (void)subtitleEditViewOnCancel:(TUIMultimediaSubtitleEditView *)view {
|
||||
if (_callback != nil && !_hasCallback) {
|
||||
_hasCallback = true;
|
||||
_callback(self, NO);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
- (void)setSubtitleInfo:(TUIMultimediaSubtitleInfo *)subtitleInfo {
|
||||
_subtitleInfo = [subtitleInfo copy];
|
||||
_editView.subtitleInfo = _subtitleInfo;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaColorCell : UICollectionViewCell
|
||||
@property(nullable, nonatomic) UIColor *color;
|
||||
@property(nonatomic) BOOL colorSelected;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame;
|
||||
|
||||
+ (NSString *)reuseIdentifier;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaColorCell.h"
|
||||
|
||||
const CGFloat ContentMarginSelected = 0;
|
||||
const CGFloat ContentMarginDeselected = 5;
|
||||
|
||||
const CGFloat FrontMargin = 4;
|
||||
|
||||
@interface TUIMultimediaColorCell () {
|
||||
UIView *_front;
|
||||
UIView *_back;
|
||||
CGFloat _contentMargin;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaColorCell
|
||||
|
||||
+ (NSString *)reuseIdentifier {
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_contentMargin = ContentMarginDeselected;
|
||||
_color = UIColor.blackColor;
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
_back = [[UIView alloc] init];
|
||||
_back.backgroundColor = UIColor.whiteColor;
|
||||
[self.contentView addSubview:_back];
|
||||
|
||||
_front = [[UIView alloc] init];
|
||||
_front.backgroundColor = _color;
|
||||
[self.contentView addSubview:_front];
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
CGFloat len = MIN(self.contentView.bounds.size.width, self.contentView.bounds.size.height) - _contentMargin;
|
||||
CGPoint center = CGPointMake(self.contentView.bounds.size.width / 2, self.contentView.bounds.size.height / 2);
|
||||
|
||||
_back.bounds = CGRectMake(0, 0, len, len);
|
||||
_back.center = center;
|
||||
_back.layer.cornerRadius = len / 2;
|
||||
_back.clipsToBounds = YES;
|
||||
|
||||
_front.bounds = CGRectMake(0, 0, len - FrontMargin, len - FrontMargin);
|
||||
_front.center = center;
|
||||
_front.layer.cornerRadius = (len - FrontMargin) / 2;
|
||||
_front.clipsToBounds = YES;
|
||||
}
|
||||
|
||||
- (void)setColor:(UIColor *)color {
|
||||
_color = color;
|
||||
_front.backgroundColor = _color;
|
||||
}
|
||||
|
||||
- (void)setColorSelected:(BOOL)colorSelected {
|
||||
_colorSelected = colorSelected;
|
||||
_contentMargin = colorSelected ? ContentMarginSelected : ContentMarginDeselected;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaImageCell : UICollectionViewCell
|
||||
@property(nullable, nonatomic) UIImage *image;
|
||||
- (instancetype)initWithFrame:(CGRect)frame;
|
||||
|
||||
+ (NSString *)reuseIdentifier;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaImageCell.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
@interface TUIMultimediaImageCell () {
|
||||
UIImageView *_imgView;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaImageCell
|
||||
|
||||
@dynamic image;
|
||||
|
||||
+ (NSString *)reuseIdentifier {
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
_imgView = [[UIImageView alloc] init];
|
||||
_imgView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self.contentView addSubview:_imgView];
|
||||
|
||||
[_imgView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
- (UIImage *)image {
|
||||
return _imgView.image;
|
||||
}
|
||||
- (void)setImage:(UIImage *)image {
|
||||
_imgView.image = image;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaStickerView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaPasterView : TUIMultimediaStickerView
|
||||
@property(nullable, nonatomic) UIImage *paster;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPasterView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaStickerView.h"
|
||||
|
||||
#define PASTER_INITIAL_WIDTH 150
|
||||
|
||||
@interface TUIMultimediaPasterView () {
|
||||
UIImageView *_imgView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPasterView
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
_imgView = [[UIImageView alloc] init];
|
||||
_imgView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.editButtonHidden = YES;
|
||||
self.content = _imgView;
|
||||
}
|
||||
- (UIImage *)paster {
|
||||
return _imgView.image;
|
||||
}
|
||||
|
||||
- (void)setPaster:(UIImage *)paster {
|
||||
self.content = nil;
|
||||
_imgView.image = paster;
|
||||
int width = MIN(PASTER_INITIAL_WIDTH, paster.size.width);
|
||||
int height = width / paster.size.width * paster.size.height;
|
||||
_imgView.bounds = CGRectMake(0, 0, width, height);
|
||||
self.content = _imgView;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaStickerViewDelegate;
|
||||
|
||||
@interface TUIMultimediaStickerView : UIView
|
||||
@property(nullable, nonatomic) UIView *content;
|
||||
@property(nonatomic) BOOL selected;
|
||||
@property(nonatomic) BOOL editButtonHidden;
|
||||
@property(nonatomic, readonly) CGFloat rotation;
|
||||
@property(nonatomic) CGFloat contentMargin;
|
||||
// 旋转夹角小于此值时,吸附到水平和竖直方向
|
||||
@property(nonatomic) CGFloat rotateAdsorptionLimitAngle;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaStickerViewDelegate> delegate;
|
||||
|
||||
- (void)scale:(CGFloat) scale;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaStickerViewDelegate <NSObject>
|
||||
- (void)onStickerViewSelected:(TUIMultimediaStickerView *)v;
|
||||
- (void)onStickerViewShouldDelete:(TUIMultimediaStickerView *)v;
|
||||
- (void)onStickerViewShouldEdit:(TUIMultimediaStickerView *)v;
|
||||
- (void)onStickerViewSizeChanged:(TUIMultimediaStickerView *)v;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,255 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaStickerView.h"
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "Masonry/Masonry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
|
||||
@interface TUIMultimediaStickerView () <UIGestureRecognizerDelegate> {
|
||||
UIButton *_btnEdit;
|
||||
UIButton *_btnTransform;
|
||||
UIButton *_btnDelete;
|
||||
UIView *_borderView;
|
||||
BOOL _hideEditButton;
|
||||
BOOL _active;
|
||||
|
||||
CGFloat _rawRotation;
|
||||
|
||||
UITapGestureRecognizer *_tapRec;
|
||||
UITapGestureRecognizer *_doubleTapRec;
|
||||
UIPanGestureRecognizer *_panRec;
|
||||
UIPinchGestureRecognizer *_pinchRec;
|
||||
UIRotationGestureRecognizer *_rotationRec;
|
||||
UIPanGestureRecognizer *_btnTransformPanRec;
|
||||
UIImpactFeedbackGenerator *_impactGen;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaStickerView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_contentMargin = 5;
|
||||
_rotateAdsorptionLimitAngle = M_PI / 180 * 5;
|
||||
_impactGen = [[UIImpactFeedbackGenerator alloc] init];
|
||||
[self frv_initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)frv_initUI {
|
||||
_borderView = [[UIView alloc] init];
|
||||
_borderView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_borderView.layer.borderWidth = 1;
|
||||
_borderView.layer.borderColor = UIColor.whiteColor.CGColor;
|
||||
_borderView.backgroundColor = [UIColor clearColor];
|
||||
_borderView.hidden = YES;
|
||||
[self addSubview:_borderView];
|
||||
[_borderView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
_btnEdit = [self newCornerButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"sticker_edit_img", @"round_edit") left:YES top:YES];
|
||||
_btnTransform = [self newCornerButtonWithImage:[TUIMultimediaImageUtil rotateImage:TUIMultimediaPluginBundleThemeImage(@"sticker_transform_img", @"round_transform") angle:90.0f] left:NO top:NO];
|
||||
_btnDelete = [self newCornerButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"sticker_delete_img", @"round_close") left:NO top:YES];
|
||||
|
||||
[_btnEdit addTarget:self action:@selector(onBtnEditClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnDelete addTarget:self action:@selector(onBtnDeleteClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
_tapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)];
|
||||
[self addGestureRecognizer:_tapRec];
|
||||
|
||||
_panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
|
||||
_panRec.delegate = self;
|
||||
[self addGestureRecognizer:_panRec];
|
||||
|
||||
_pinchRec = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(onPinch:)];
|
||||
_pinchRec.delegate = self;
|
||||
[self addGestureRecognizer:_pinchRec];
|
||||
|
||||
_rotationRec = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(onGestureRotation:)];
|
||||
_rotationRec.delegate = self;
|
||||
[self addGestureRecognizer:_rotationRec];
|
||||
|
||||
_btnTransformPanRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onBtnTransformPan:)];
|
||||
[_btnTransform addGestureRecognizer:_btnTransformPanRec];
|
||||
|
||||
_doubleTapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onDoubleTap:)];
|
||||
_doubleTapRec.delaysTouchesEnded = NO;
|
||||
_doubleTapRec.numberOfTapsRequired = 2;
|
||||
[self addGestureRecognizer:_doubleTapRec];
|
||||
}
|
||||
|
||||
- (UIButton *)newCornerButtonWithImage:(UIImage *)image left:(BOOL)left top:(BOOL)top {
|
||||
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
btn.hidden = YES;
|
||||
[self addSubview:btn];
|
||||
btn.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[btn setImage:image forState:UIControlStateNormal];
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.height.mas_equalTo(24);
|
||||
make.centerX.equalTo(left ? self.mas_left : self.mas_right);
|
||||
make.centerY.equalTo(top ? self.mas_top : self.mas_bottom);
|
||||
}];
|
||||
return btn;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[_delegate onStickerViewSizeChanged:self];
|
||||
}
|
||||
|
||||
- (void)updateRotation {
|
||||
// 规范化到[0,2pi)
|
||||
_rawRotation -= floor(_rawRotation / (M_PI * 2)) * M_PI * 2;
|
||||
BOOL adsorped = NO;
|
||||
// 吸附到 0,90,180,270度
|
||||
for (int i = 0; i <= 3; i++) {
|
||||
CGFloat diff = fabs(_rawRotation - i * M_PI_2);
|
||||
CGFloat diff2 = fabs(_rawRotation - M_PI * 2 - i * M_PI_2);
|
||||
if (diff < _rotateAdsorptionLimitAngle || diff2 < _rotateAdsorptionLimitAngle) {
|
||||
adsorped = YES;
|
||||
if (_rotation != i * M_PI_2) {
|
||||
_rotation = i * M_PI_2;
|
||||
[_impactGen impactOccurred];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!adsorped) {
|
||||
_rotation = _rawRotation;
|
||||
}
|
||||
self.transform = CGAffineTransformMakeRotation(_rotation);
|
||||
NSLog(@"updateRotation %@",NSStringFromCGRect(self.frame));
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)onTap {
|
||||
if (!self.selected) {
|
||||
self.selected = YES;
|
||||
[_delegate onStickerViewSelected:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onDoubleTap:(UITapGestureRecognizer *)rec {
|
||||
[_delegate onStickerViewShouldEdit:self];
|
||||
}
|
||||
|
||||
- (void)onPan:(UIPanGestureRecognizer *)rec {
|
||||
CGPoint offset = [rec translationInView:self.superview];
|
||||
[rec setTranslation:CGPointZero inView:self.superview];
|
||||
// 平移
|
||||
self.center = Vec2AddVector(self.center, offset);
|
||||
NSLog(@"onPan %@",NSStringFromCGRect(self.frame));
|
||||
}
|
||||
|
||||
|
||||
- (void)onPinch:(UIPinchGestureRecognizer *)rec {
|
||||
// pow用于加快放大/缩小速度,优化体验
|
||||
CGFloat scale = pow(rec.scale, 1.5);
|
||||
rec.scale = 1;
|
||||
CGSize raw = self.bounds.size;
|
||||
self.bounds = CGRectMake(0, 0, raw.width * scale, raw.height * scale);
|
||||
NSLog(@"onPinch %@",NSStringFromCGRect(self.frame));
|
||||
}
|
||||
|
||||
- (void)scale:(CGFloat) scale {
|
||||
CGSize raw = self.bounds.size;
|
||||
self.bounds = CGRectMake(0, 0, raw.width * scale, raw.height * scale);
|
||||
self.center = CGPointMake(self.center.x * scale, self.center.y * scale);
|
||||
}
|
||||
|
||||
- (void)onGestureRotation:(UIRotationGestureRecognizer *)rec {
|
||||
_rawRotation += rec.rotation;
|
||||
[self updateRotation];
|
||||
rec.rotation = 0;
|
||||
}
|
||||
|
||||
- (void)onBtnTransformPan:(UIPanGestureRecognizer *)rec {
|
||||
CGPoint offset = [rec translationInView:self.superview];
|
||||
[rec setTranslation:CGPointZero inView:self.superview];
|
||||
|
||||
CGPoint originSize = CGPointMake(self.bounds.size.width, self.bounds.size.height);
|
||||
CGPoint originV = Vec2Rotate(Vec2Mul(originSize, 0.5), _rawRotation);
|
||||
CGPoint newV = Vec2AddVector(originV, offset);
|
||||
// 缩放
|
||||
CGPoint newSize = Vec2Mul(originSize, Vec2Len(newV) / Vec2Len(originV));
|
||||
self.bounds = CGRectMake(0, 0, newSize.x, newSize.y);
|
||||
// 旋转
|
||||
_rawRotation += Vec2Degree(originV, newV);
|
||||
[self updateRotation];
|
||||
}
|
||||
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
if (CGRectContainsPoint(self.bounds, point)) {
|
||||
return YES;
|
||||
}
|
||||
for (UIView *v in self.subviews) {
|
||||
CGPoint localPoint = [v convertPoint:point fromView:self];
|
||||
if (CGRectContainsPoint(v.bounds, localPoint)) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)onBtnEditClicked {
|
||||
[_delegate onStickerViewShouldEdit:self];
|
||||
}
|
||||
|
||||
- (void)onBtnDeleteClicked {
|
||||
[_delegate onStickerViewShouldDelete:self];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (BOOL)editButtonHidden {
|
||||
return _hideEditButton;
|
||||
}
|
||||
|
||||
- (void)setEditButtonHidden:(BOOL)hideEditButton {
|
||||
_hideEditButton = hideEditButton;
|
||||
}
|
||||
|
||||
- (void)setContent:(UIView *)content {
|
||||
[_content removeFromSuperview];
|
||||
_content = content;
|
||||
if (content == nil) {
|
||||
return;
|
||||
}
|
||||
self.bounds = CGRectMake(0, 0, content.bounds.size.width + _contentMargin * 2, content.bounds.size.height + _contentMargin * 2);
|
||||
|
||||
|
||||
|
||||
// [self mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
// make.center.equalTo(self.superview);
|
||||
// }];
|
||||
|
||||
|
||||
[self addSubview:content];
|
||||
[self bringSubviewToFront:_btnEdit];
|
||||
[self bringSubviewToFront:_btnTransform];
|
||||
[self bringSubviewToFront:_btnDelete];
|
||||
[content mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self).inset(_contentMargin);
|
||||
}];
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (void)setSelected:(BOOL)selected {
|
||||
_selected = selected;
|
||||
_borderView.hidden = !selected;
|
||||
_btnEdit.hidden = !selected || _hideEditButton;
|
||||
_btnTransform.hidden = !selected;
|
||||
_btnDelete.hidden = !selected;
|
||||
}
|
||||
|
||||
#pragma mark - UIGestureRecognizerDelegate protocol
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)g1 shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)g2 {
|
||||
NSArray<UIGestureRecognizer *> *recList = @[ _pinchRec, _rotationRec, _panRec ];
|
||||
return [recList containsObject:g1] && [recList containsObject:g2];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaStickerView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleInfo.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaSubtitleView : TUIMultimediaStickerView
|
||||
@property(nullable, nonatomic) TUIMultimediaSubtitleInfo *subtitleInfo;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSubtitleView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
|
||||
#define MIN_SUBTITLE_PASTER_WIDTH 100
|
||||
#define MAX_SUBTITLE_PASTER_HEIGH 400
|
||||
|
||||
@interface TUIMultimediaSubtitleView () <TUIMultimediaStickerViewDelegate> {
|
||||
id<TUIMultimediaStickerViewDelegate> _outerDelegate;
|
||||
UILabel *_label;
|
||||
BOOL _isSizeToFitingSubtitleInfo;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaSubtitleView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
[self initUI];
|
||||
}
|
||||
_isSizeToFitingSubtitleInfo = NO;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
[super setDelegate:self];
|
||||
_label = [[UILabel alloc] init];
|
||||
_label.font = [UIFont systemFontOfSize:20];
|
||||
_label.numberOfLines = 0;
|
||||
_label.lineBreakMode = NSLineBreakByClipping;
|
||||
_label.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Getters & Setters
|
||||
- (void)setSubtitleInfo:(TUIMultimediaSubtitleInfo *)subtitleInfo {
|
||||
NSLog(@"TUIMultimediaSubtitleView setSubtitleInfo text = %@",subtitleInfo.wrappedText);
|
||||
_subtitleInfo = subtitleInfo;
|
||||
|
||||
self.content = nil;
|
||||
_label.text = subtitleInfo.wrappedText;
|
||||
_label.textColor = subtitleInfo.color;
|
||||
[self labeSizeToFit];
|
||||
self.content = _label;
|
||||
}
|
||||
|
||||
-(void)labeSizeToFit {
|
||||
_isSizeToFitingSubtitleInfo = YES;
|
||||
if (_subtitleInfo.wrappedText.length > 0) {
|
||||
_label.text = [NSString stringWithFormat:@"%C", [_subtitleInfo.wrappedText characterAtIndex:0]];
|
||||
} else {
|
||||
NSLog(@"_subtitleInfo.wrappedText is empty or nil.");
|
||||
_label.text = @"";
|
||||
}
|
||||
CGSize size = [_label sizeThatFits:CGSizeMake(MAXFLOAT, MAX_SUBTITLE_PASTER_HEIGH)];
|
||||
CGFloat singleLineTextHeight = size.height;
|
||||
|
||||
_label.text = _subtitleInfo.wrappedText;
|
||||
size = [_label sizeThatFits:CGSizeMake(MAXFLOAT, MAX_SUBTITLE_PASTER_HEIGH)];
|
||||
CGFloat adjustedWidth = size.width;
|
||||
if (size.height > singleLineTextHeight * 1.5) {
|
||||
// multi line text
|
||||
adjustedWidth = MAX(size.width, MIN_SUBTITLE_PASTER_WIDTH);
|
||||
}
|
||||
CGFloat adjustedHeight = MIN(size.height, MAX_SUBTITLE_PASTER_HEIGH);
|
||||
_label.frame = CGRectMake(_label.frame.origin.x, _label.frame.origin.y, adjustedWidth, adjustedHeight);
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
self->_isSizeToFitingSubtitleInfo = NO;
|
||||
});
|
||||
}
|
||||
|
||||
- (id<TUIMultimediaStickerViewDelegate>)delegate {
|
||||
return _outerDelegate;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<TUIMultimediaStickerViewDelegate>)delegate {
|
||||
_outerDelegate = delegate;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaFloatingResizableDelegate protocol
|
||||
- (void)onStickerViewShouldDelete:(TUIMultimediaStickerView *)v {
|
||||
[_outerDelegate onStickerViewShouldDelete:self];
|
||||
}
|
||||
|
||||
- (void)onStickerViewShouldEdit:(TUIMultimediaStickerView *)v {
|
||||
[_outerDelegate onStickerViewShouldEdit:self];
|
||||
}
|
||||
|
||||
- (void)onStickerViewSelected:(TUIMultimediaStickerView *)v {
|
||||
[_outerDelegate onStickerViewSelected:self];
|
||||
}
|
||||
|
||||
- (void)onStickerViewSizeChanged:(TUIMultimediaStickerView *)v {
|
||||
[_outerDelegate onStickerViewSizeChanged:self];
|
||||
if (_isSizeToFitingSubtitleInfo == NO) {
|
||||
[self adjustFontSize];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)adjustFontSize {
|
||||
NSString *text = _label.text;
|
||||
CGSize labelSize = _label.bounds.size;
|
||||
UIFont *font = _label.font;
|
||||
CGFloat minFontSize = 0;
|
||||
CGFloat maxFontSize = 1000;
|
||||
const CGFloat eps = 0.1;
|
||||
// 理论上需要14次二分
|
||||
while (maxFontSize - minFontSize > eps) {
|
||||
CGFloat mid = (maxFontSize + minFontSize) / 2;
|
||||
font = [font fontWithSize:mid];
|
||||
CGSize size = [text sizeWithAttributes:@{NSFontAttributeName : font}];
|
||||
if (size.width < labelSize.width && size.height < labelSize.height) {
|
||||
minFontSize = mid;
|
||||
} else {
|
||||
maxFontSize = mid;
|
||||
}
|
||||
}
|
||||
_label.font = [_label.font fontWithSize:minFontSize];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaColorPanelDelegate;
|
||||
|
||||
/**
|
||||
条形颜色选择器
|
||||
*/
|
||||
@interface TUIMultimediaColorPanel : UIView
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaColorPanelDelegate> delegate;
|
||||
@property(nonatomic) NSArray<UIColor *> *colorList;
|
||||
@property(nonatomic) UIColor *selectedColor;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaColorPanelDelegate <NSObject>
|
||||
- (void)onColorPanel:(TUIMultimediaColorPanel *)panel selectColor:(UIColor *)color;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaColorPanel.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorCell.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
|
||||
const static CGFloat CellMargin = 4;
|
||||
|
||||
@interface TUIMultimediaColorPanel () <UICollectionViewDelegateFlowLayout, UICollectionViewDataSource> {
|
||||
UICollectionView *_collectionView;
|
||||
NSInteger _selectIndex;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaColorPanel
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_colorList = @[
|
||||
UIColor.whiteColor,
|
||||
UIColor.blackColor,
|
||||
UIColor.grayColor,
|
||||
UIColor.redColor,
|
||||
UIColor.greenColor,
|
||||
UIColor.blueColor,
|
||||
UIColor.cyanColor,
|
||||
UIColor.yellowColor,
|
||||
UIColor.magentaColor,
|
||||
UIColor.orangeColor,
|
||||
UIColor.purpleColor,
|
||||
UIColor.brownColor,
|
||||
];
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
layout.minimumInteritemSpacing = 0;
|
||||
layout.minimumLineSpacing = 2;
|
||||
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
_collectionView.backgroundColor = UIColor.clearColor;
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
[_collectionView registerClass:TUIMultimediaColorCell.class forCellWithReuseIdentifier:TUIMultimediaColorCell.reuseIdentifier];
|
||||
[self addSubview:_collectionView];
|
||||
|
||||
[_collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource protocol
|
||||
- (nonnull __kindof UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
|
||||
TUIMultimediaColorCell *cell = (TUIMultimediaColorCell *)[collectionView dequeueReusableCellWithReuseIdentifier:TUIMultimediaColorCell.reuseIdentifier forIndexPath:indexPath];
|
||||
cell.color = _colorList[indexPath.item];
|
||||
cell.colorSelected = indexPath.item == _selectIndex;
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return _colorList.count;
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegateFlowLayout protocol
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[collectionView deselectItemAtIndexPath:indexPath animated:NO];
|
||||
_selectIndex = indexPath.item;
|
||||
[_collectionView reloadData];
|
||||
|
||||
UIColor *c = _colorList[indexPath.item];
|
||||
[_delegate onColorPanel:self selectColor:c];
|
||||
}
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return YES;
|
||||
}
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)index {
|
||||
CGFloat len = collectionView.bounds.size.height - CellMargin;
|
||||
return CGSizeMake(len, len);
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
- (void)setColorList:(NSArray<UIColor *> *)colorList {
|
||||
_colorList = colorList;
|
||||
_selectIndex = 0;
|
||||
if (_colorList.count > 0) {
|
||||
[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathWithIndex:0]].selected = YES;
|
||||
}
|
||||
[_collectionView reloadData];
|
||||
}
|
||||
|
||||
- (UIColor *)selectedColor {
|
||||
if (_selectIndex < 0 || _selectIndex >= _colorList.count) {
|
||||
return nil;
|
||||
}
|
||||
return _colorList[_selectIndex];
|
||||
}
|
||||
|
||||
- (void)setSelectedColor:(UIColor *)selectedColor {
|
||||
if (selectedColor == nil) {
|
||||
_selectIndex = -1;
|
||||
} else {
|
||||
for (int i = 0; i < _colorList.count; i++) {
|
||||
if ([_colorList[i] isEqual:selectedColor]) {
|
||||
_selectIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
[_collectionView reloadData];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#include "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
#include "TUIMultimediaPlugin/TUIMultimediaSticker.h"
|
||||
#include "TUIMultimediaPlugin/TUIMultimediaSubtitleInfo.h"
|
||||
#include "TUIMultimediaStickerView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaCommonEditorControlViewDelegate;
|
||||
@class TUIMultimediaCommonEditorConfig;
|
||||
|
||||
/**
|
||||
编辑页面View
|
||||
*/
|
||||
@interface TUIMultimediaCommonEditorControlView : UIView
|
||||
@property(readonly, nonatomic) UIView *previewView;
|
||||
@property(nonatomic) BOOL isGenerating;
|
||||
@property(nonatomic) CGFloat progressBarProgress;
|
||||
@property(nonatomic) BOOL musicEdited;
|
||||
@property(nonatomic) CGSize previewSize;
|
||||
@property(nonatomic) BOOL modifyButtonsHidden;
|
||||
@property(nonatomic) BOOL isStartCrop;
|
||||
@property(nonatomic) int sourceType;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaCommonEditorControlViewDelegate> delegate;
|
||||
@property (nonatomic, strong) UIImage *mosaciOriginalImage;
|
||||
@property(nonatomic) CGRect previewLimitRect;
|
||||
|
||||
- (instancetype)initWithConfig:(TUIMultimediaCommonEditorConfig *)config;
|
||||
|
||||
- (void)addPaster:(UIImage *)paster;
|
||||
- (void)addSubtitle:(TUIMultimediaSubtitleInfo *)subtitle;
|
||||
|
||||
// crop相关
|
||||
- (void)previewScale:(CGFloat)scale center:(CGPoint)center;
|
||||
- (void)previewMove:(CGPoint)offset;
|
||||
- (void)previewRotation90:(CGPoint)center;
|
||||
- (void)previewRotationToZero;
|
||||
- (void)previewAdjustToLimitRect;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaCommonEditorControlViewDelegate <NSObject>
|
||||
- (void)onCommonEditorControlViewComplete:(TUIMultimediaCommonEditorControlView *)view stickers:(NSArray<TUIMultimediaSticker *> *)stickers;
|
||||
- (void)onCommonEditorControlViewCrop:(NSInteger)rotationAngle normalizedCropRect:(CGRect)normalizedCropRect
|
||||
stickers:(NSArray<TUIMultimediaSticker *> *)stickers;
|
||||
- (void)onCommonEditorControlViewCancel:(TUIMultimediaCommonEditorControlView *)view;
|
||||
- (void)onCommonEditorControlViewNeedAddPaster:(TUIMultimediaCommonEditorControlView *)view;
|
||||
- (void)onCommonEditorControlViewNeedModifySubtitle:(TUIMultimediaSubtitleInfo *)info callback:(void (^)(TUIMultimediaSubtitleInfo *newInfo, BOOL isOk))callback;
|
||||
- (void)onCommonEditorControlViewNeedEditMusic:(TUIMultimediaCommonEditorControlView *)view;
|
||||
- (void)onCommonEditorControlViewCancelGenerate:(TUIMultimediaCommonEditorControlView *)view;
|
||||
@end
|
||||
|
||||
@interface TUIMultimediaCommonEditorConfig : NSObject
|
||||
@property(nonatomic) BOOL pasterEnabled;
|
||||
@property(nonatomic) BOOL subtitleEnabled;
|
||||
@property(nonatomic) BOOL drawGraffitiEnabled;
|
||||
@property(nonatomic) BOOL drawMosaicEnabled;
|
||||
@property(nonatomic) BOOL musicEditEnabled;
|
||||
@property(nonatomic) BOOL cropEnabled;
|
||||
+ (instancetype)configForVideoEditor;
|
||||
+ (instancetype)configForPictureEditor;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,701 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaCommonEditorControlView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCircleProgressView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterSelectView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSplitter.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaStickerView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaTabPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConstant.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCropControlView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawCtrlView.h"
|
||||
|
||||
#define FUNCTION_BUTTON_SIZE CGSizeMake(28, 28)
|
||||
#define BUTTON_SEND_SIZE CGSizeMake(60, 28)
|
||||
|
||||
@interface TUIMultimediaCommonEditorControlView () <TUIMultimediaStickerViewDelegate, UIGestureRecognizerDelegate, TUIMultimediaDrawCtrlViewDelegate, TUIMultimediaCropControlDelegate> {
|
||||
TUIMultimediaCommonEditorConfig *_config;
|
||||
UIStackView *_stkViewButtons;
|
||||
UIButton *_btnDrawGraffiti;
|
||||
UIButton *_btnDrawMosaic;
|
||||
UIButton *_btnMusic;
|
||||
UIButton *_btnSubtitle;
|
||||
UIButton *_btnPaster;
|
||||
UIButton *_btnCrop;
|
||||
UIButton *_btnSend;
|
||||
UIButton *_btnCancel;
|
||||
|
||||
NSMutableArray<TUIMultimediaStickerView *> *_stickerViewList;
|
||||
TUIMultimediaStickerView *_lastSelectedStickerView;
|
||||
UIView *_editContainerView;
|
||||
|
||||
TUIMultimediaDrawView *_drawView;
|
||||
TUIMultimediaDrawCtrlView *_drawCtrlView;
|
||||
|
||||
UIView *_generateView;
|
||||
TUIMultimediaCircleProgressView *_progressView;
|
||||
UIButton *_btnGenerateCancel;
|
||||
|
||||
BOOL _isStartCrop;
|
||||
NSInteger _previewRotationAngle;
|
||||
TUIMultimediaCropControlView* _cropControlView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaCommonEditorControlView
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
return [self initWithConfig:[[TUIMultimediaCommonEditorConfig alloc] init]];
|
||||
}
|
||||
- (instancetype)initWithConfig:(TUIMultimediaCommonEditorConfig *)config {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self != nil) {
|
||||
_config = config;
|
||||
_sourceType = SOURCE_TYPE_RECORD;
|
||||
_isStartCrop = NO;
|
||||
_stickerViewList = [NSMutableArray array];
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addPaster:(UIImage *)paster {
|
||||
TUIMultimediaPasterView *vpaster = [[TUIMultimediaPasterView alloc] init];
|
||||
vpaster.delegate = self;
|
||||
[_stickerViewList addObject:vpaster];
|
||||
vpaster.paster = paster;
|
||||
vpaster.center = CGPointMake(_editContainerView.bounds.size.width / 2, _editContainerView.bounds.size.height / 2);
|
||||
[_editContainerView addSubview:vpaster];
|
||||
}
|
||||
|
||||
- (void)addSubtitle:(TUIMultimediaSubtitleInfo *)subtitle {
|
||||
TUIMultimediaSubtitleView *vsub = [[TUIMultimediaSubtitleView alloc] init];
|
||||
vsub.delegate = self;
|
||||
[_stickerViewList addObject:vsub];
|
||||
vsub.subtitleInfo = subtitle;
|
||||
vsub.center = CGPointMake(_editContainerView.bounds.size.width / 2, _editContainerView.bounds.size.height / 2);
|
||||
[_editContainerView addSubview:vsub];
|
||||
}
|
||||
|
||||
#pragma mark crop function
|
||||
- (void)previewScale:(CGFloat) scale center:(CGPoint)center {
|
||||
if (_isStartCrop) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat width = _previewView.frame.size.width * scale;
|
||||
CGFloat height = _previewView.frame.size.height * scale;
|
||||
|
||||
CGFloat left = center.x - (center.x - _previewView.frame.origin.x) * scale;
|
||||
CGFloat top = center.y - (center.y - _previewView.frame.origin.y) * scale;
|
||||
_previewView.frame = CGRectMake(left, top, width, height);
|
||||
|
||||
for (UIView *subview in _editContainerView.subviews) {
|
||||
if ([subview isKindOfClass:[TUIMultimediaStickerView class]]) {
|
||||
TUIMultimediaStickerView* stickView = (TUIMultimediaStickerView*)subview;
|
||||
[stickView scale:scale];
|
||||
}
|
||||
}
|
||||
|
||||
if (_cropControlView != nil) {
|
||||
[_cropControlView changeResetButtonStatus];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)previewMove:(CGPoint)offset {
|
||||
if (_isStartCrop) {
|
||||
return;
|
||||
}
|
||||
_previewView.frame = CGRectMake(_previewView.frame.origin.x + offset.x,
|
||||
_previewView.frame.origin.y + offset.y,
|
||||
_previewView.frame.size.width,
|
||||
_previewView.frame.size.height);
|
||||
if (_cropControlView != nil) {
|
||||
[_cropControlView changeResetButtonStatus];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)previewRotation90:(CGPoint)center {
|
||||
_previewRotationAngle = (_previewRotationAngle + 90 + 360) % 360;
|
||||
float radians = _previewRotationAngle * M_PI / 180;
|
||||
int newTop = center.y - center.x + _previewView.frame.origin.x;
|
||||
int newLeft = center.x + center.y - _previewView.frame.origin.y - _previewView.frame.size.height;
|
||||
|
||||
_editContainerView.transform = CGAffineTransformMakeRotation(radians);
|
||||
_previewView.transform = CGAffineTransformMakeRotation(radians);
|
||||
_previewView.frame = CGRectMake(newLeft, newTop, _previewView.frame.size.width, _previewView.frame.size.height);
|
||||
|
||||
if (_cropControlView != nil) {
|
||||
[_cropControlView changeResetButtonStatus];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)previewRotationToZero {
|
||||
_previewRotationAngle = 0;
|
||||
_editContainerView.transform = CGAffineTransformMakeRotation(0);
|
||||
_previewView.transform = CGAffineTransformMakeRotation(0);
|
||||
}
|
||||
|
||||
- (void)previewAdjustToLimitRect {
|
||||
NSLog(@"preview adjust to limit rect");
|
||||
double scaleLimitX = _previewLimitRect.size.width * 1.0f / _previewView.frame.size.width;
|
||||
double scaleLimitY = _previewLimitRect.size.height * 1.0f / _previewView.frame.size.height;
|
||||
double scaleLimit = MAX(scaleLimitX, scaleLimitY);
|
||||
if (scaleLimit > 1.0f) {
|
||||
[self previewScale:scaleLimit center:CGPointMake(CGRectGetMidX(_previewView.frame), CGRectGetMidY(_previewView.frame))];
|
||||
}
|
||||
|
||||
|
||||
int maxLeft = _previewLimitRect.origin.x;
|
||||
int minLeft = _previewLimitRect.size.width + _previewLimitRect.origin.x - _previewView.frame.size.width;
|
||||
int left = MAX(MIN(_previewView.frame.origin.x , maxLeft), minLeft);
|
||||
int maxTop = _previewLimitRect.origin.y;
|
||||
int minTop = _previewLimitRect.size.height + _previewLimitRect.origin.y - _previewView.frame.size.height;
|
||||
int top = MAX(MIN(_previewView.frame.origin.y , maxTop), minTop);
|
||||
_previewView.frame = CGRectMake(left, top, _previewView.frame.size.width, _previewView.frame.size.height);
|
||||
}
|
||||
|
||||
#pragma mark UI init
|
||||
- (void)initUI {
|
||||
self.backgroundColor = UIColor.blackColor;
|
||||
|
||||
_previewView = [[UIView alloc] init];
|
||||
[self addSubview:_previewView];
|
||||
|
||||
[self initFuncitonBtnStackView];
|
||||
[self initGraffitiFunction];
|
||||
[self initMosaicFunction];
|
||||
[self initPasterFunciton];
|
||||
[self initSubtitleFunciton];
|
||||
[self initBGMFunction];
|
||||
[self initCropFunciton];
|
||||
[self initSendAndCancelBtn];
|
||||
[self initGenerateView];
|
||||
[self bringSubviewToFront:_stkViewButtons];
|
||||
}
|
||||
|
||||
- (void) initFuncitonBtnStackView {
|
||||
_stkViewButtons = [[UIStackView alloc] init];
|
||||
_stkViewButtons.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_stkViewButtons];
|
||||
_stkViewButtons.axis = UILayoutConstraintAxisHorizontal;
|
||||
_stkViewButtons.alignment = UIStackViewAlignmentCenter;
|
||||
_stkViewButtons.distribution = UIStackViewDistributionEqualSpacing;
|
||||
[_stkViewButtons mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self).inset(10);
|
||||
make.bottom.equalTo(self.mas_safeAreaLayoutGuideBottom);
|
||||
make.height.mas_equalTo(FUNCTION_BUTTON_SIZE.height);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) initGraffitiFunction {
|
||||
if (!_config.drawGraffitiEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_btnDrawGraffiti = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_scrawl_img", @"modify_scrawl")
|
||||
onTouchUpInside:@selector(onBtnDrawGraffitiClicked)];
|
||||
|
||||
[self initEditContainerView];
|
||||
[self initDrawCtrlView];
|
||||
}
|
||||
|
||||
- (void) initMosaicFunction {
|
||||
if (!_config.drawMosaicEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_btnDrawMosaic = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"edit_mosaic_img", @"edit_mosaic")
|
||||
onTouchUpInside:@selector(onBtnDrawMosaicClicked)];
|
||||
|
||||
[self initEditContainerView];
|
||||
[self initDrawCtrlView];
|
||||
_drawCtrlView.drawView.mosaciOriginalImage = _mosaciOriginalImage;
|
||||
}
|
||||
|
||||
- (void) initPasterFunciton {
|
||||
if (!_config.pasterEnabled) {
|
||||
return;
|
||||
}
|
||||
_btnPaster = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_paster_img", @"modify_paster")
|
||||
onTouchUpInside:@selector(onBtnPasterClicked)];
|
||||
[self initEditContainerView];
|
||||
}
|
||||
|
||||
- (void) initSubtitleFunciton {
|
||||
if (!_config.subtitleEnabled) {
|
||||
return;
|
||||
}
|
||||
_btnSubtitle = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_subtitle_img", @"modify_subtitle")
|
||||
onTouchUpInside:@selector(onBtnSubtitleClicked)];
|
||||
[self initEditContainerView];
|
||||
}
|
||||
|
||||
- (void) initBGMFunction {
|
||||
if (!_config.musicEditEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_btnMusic = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_music_img", @"modify_music")
|
||||
onTouchUpInside:@selector(onBtnMusicClicked)];
|
||||
}
|
||||
|
||||
- (void) initCropFunciton {
|
||||
if (!_config.cropEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_btnCrop = [self addFunctionIconButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_crop_img", @"modify_crop")
|
||||
onTouchUpInside:@selector(onBtnCropClicked)];
|
||||
[self initCropCtrlView];
|
||||
}
|
||||
|
||||
- (void)initCropCtrlView {
|
||||
if (_cropControlView != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
_cropControlView = [[TUIMultimediaCropControlView alloc] initWithFrame:self.frame editorControl:self];
|
||||
[self addSubview:_cropControlView];
|
||||
_cropControlView.hidden = YES;
|
||||
[_cropControlView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
_cropControlView.delegate = self;
|
||||
}
|
||||
|
||||
- (void)initDrawCtrlView {
|
||||
if (_drawCtrlView != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
_drawCtrlView = [[TUIMultimediaDrawCtrlView alloc] init];
|
||||
[self addSubview:_drawCtrlView];
|
||||
[_drawCtrlView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self);
|
||||
make.top.equalTo(_stkViewButtons).offset(-50);
|
||||
make.bottom.equalTo(self);
|
||||
}];
|
||||
_drawCtrlView.drawEnable = NO;
|
||||
_drawCtrlView.delegate = self;
|
||||
|
||||
[_editContainerView addSubview:_drawCtrlView.drawView];
|
||||
[_drawCtrlView.drawView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(_previewView);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) initEditContainerView {
|
||||
if (_editContainerView != nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
_editContainerView = [[UIView alloc] init];
|
||||
[self addSubview:_editContainerView];
|
||||
_editContainerView.userInteractionEnabled = YES;
|
||||
_editContainerView.clipsToBounds = YES;
|
||||
|
||||
UITapGestureRecognizer *containerTapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapContainerView:)];
|
||||
containerTapRec.cancelsTouchesInView = NO;
|
||||
[_editContainerView addGestureRecognizer:containerTapRec];
|
||||
[_editContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(_previewView);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) initSendAndCancelBtn {
|
||||
_btnSend = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
|
||||
if (_stkViewButtons.arrangedSubviews.count == 0) {
|
||||
[_stkViewButtons removeFromSuperview];
|
||||
[self addSubview:_btnSend];
|
||||
[_btnSend mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(BUTTON_SEND_SIZE);
|
||||
make.right.equalTo(self).inset(20);
|
||||
make.bottom.equalTo(self.mas_safeAreaLayoutGuideBottom);
|
||||
}];
|
||||
} else {
|
||||
[_stkViewButtons addArrangedSubview:_btnSend];
|
||||
[_btnSend mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(BUTTON_SEND_SIZE);
|
||||
}];
|
||||
}
|
||||
|
||||
_btnSend.backgroundColor = [[TUIMultimediaConfig sharedInstance] getThemeColor];
|
||||
[_btnSend setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
_btnSend.layer.cornerRadius = 5;
|
||||
NSString* titile = [TUIMultimediaCommon localizedStringForKey:@"send"];
|
||||
if (_sourceType == SOURCE_TYPE_ALBUM) {
|
||||
titile = [TUIMultimediaCommon localizedStringForKey:@"done"];
|
||||
}
|
||||
[_btnSend setTitle:titile forState:UIControlStateNormal];
|
||||
[_btnSend addTarget:self action:@selector(onBtnSendClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
|
||||
_btnCancel = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[self addSubview:_btnCancel];
|
||||
[_btnCancel setImage:TUIMultimediaPluginBundleThemeImage(@"editor_cancel_img", @"return_arrow") forState:UIControlStateNormal];
|
||||
[_btnCancel addTarget:self action:@selector(onBtnCancelClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnCancel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(CGSizeMake(24, 24));
|
||||
make.top.equalTo(self.mas_safeAreaLayoutGuideTop).inset(10);
|
||||
if([[TUIGlobalization getPreferredLanguage] hasPrefix:@"ar"]) {
|
||||
make.right.equalTo(self.mas_safeAreaLayoutGuideRight).offset(-15);
|
||||
} else {
|
||||
make.left.equalTo(self.mas_safeAreaLayoutGuideLeft).offset(15);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setSourceType:(int)sourceType {
|
||||
NSLog(@"setSourceType sourceType = %d",sourceType);
|
||||
_sourceType = sourceType;
|
||||
NSString* titile = [TUIMultimediaCommon localizedStringForKey:@"send"];
|
||||
if (_sourceType == SOURCE_TYPE_ALBUM) {
|
||||
titile = [TUIMultimediaCommon localizedStringForKey:@"done"];
|
||||
}
|
||||
[_btnSend setTitle:titile forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
- (void)initGenerateView {
|
||||
_generateView = [[UIView alloc] init];
|
||||
_generateView.backgroundColor = TUIMultimediaPluginDynamicColor(@"editor_generate_view_bg_color", @"#0000007F");
|
||||
_generateView.hidden = YES;
|
||||
[self addSubview:_generateView];
|
||||
|
||||
_progressView = [[TUIMultimediaCircleProgressView alloc] init];
|
||||
_progressView.progressColor = [[TUIMultimediaConfig sharedInstance] getThemeColor];
|
||||
[_generateView addSubview:_progressView];
|
||||
|
||||
_btnGenerateCancel = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[_btnGenerateCancel addTarget:self action:@selector(onBtnGenerateCancelClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnGenerateCancel setTitle:[TUIMultimediaCommon localizedStringForKey:@"cancel"] forState:UIControlStateNormal];
|
||||
[_btnGenerateCancel setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
[_generateView addSubview:_btnGenerateCancel];
|
||||
|
||||
[_generateView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
[_progressView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(CGSizeMake(100, 100));
|
||||
make.center.equalTo(_generateView);
|
||||
}];
|
||||
[_btnGenerateCancel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.mas_safeAreaLayoutGuideTop).inset(10);
|
||||
make.left.equalTo(self).inset(10);
|
||||
make.size.mas_equalTo(CGSizeMake(54, 32));
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIButton *)addFunctionIconButtonWithImage:(UIImage *)img onTouchUpInside:(SEL)sel {
|
||||
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_stkViewButtons addArrangedSubview:btn];
|
||||
UIImage *imgNormal = [TUIMultimediaImageUtil imageFromImage:img withTintColor:
|
||||
TUIMultimediaPluginDynamicColor(@"editor_func_btn_normal_color", @"#FFFFFF")];
|
||||
UIImage *imgDisabled = [TUIMultimediaImageUtil imageFromImage:img withTintColor:
|
||||
TUIMultimediaPluginDynamicColor(@"editor_func_btn_disabled_color", @"#6D6D6D")];
|
||||
UIImage *imgPressed = [TUIMultimediaImageUtil imageFromImage:img withTintColor:
|
||||
TUIMultimediaPluginDynamicColor(@"editor_func_btn_pressed_color", @"#7F7F7F")];
|
||||
UIImage *imgSelected = [TUIMultimediaImageUtil imageFromImage:img withTintColor:
|
||||
[[TUIMultimediaConfig sharedInstance] getThemeColor]];
|
||||
[btn setImage:imgNormal forState:UIControlStateNormal];
|
||||
[btn setImage:imgDisabled forState:UIControlStateDisabled];
|
||||
[btn setImage:imgPressed forState:UIControlStateHighlighted];
|
||||
[btn setImage:imgSelected forState:UIControlStateSelected];
|
||||
[btn setImageEdgeInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
|
||||
[btn addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.mas_equalTo(FUNCTION_BUTTON_SIZE);
|
||||
}];
|
||||
return btn;
|
||||
}
|
||||
|
||||
#pragma mark Internal Functions
|
||||
- (void)setDrawEnabled:(BOOL)drawGraffitiEnabled {
|
||||
if (!_config.drawGraffitiEnabled) {
|
||||
return;
|
||||
}
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
_btnDrawGraffiti.selected = drawGraffitiEnabled;
|
||||
if(drawGraffitiEnabled) {
|
||||
_btnDrawMosaic.selected = NO;
|
||||
}
|
||||
_drawCtrlView.drawMode = GRAFFITI;
|
||||
_drawCtrlView.drawEnable = drawGraffitiEnabled;
|
||||
for (TUIMultimediaStickerView *v in _stickerViewList) {
|
||||
v.userInteractionEnabled = !drawGraffitiEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDrawMosaicEnabled:(BOOL)drawMosaicEnabled {
|
||||
if (!_config.drawMosaicEnabled) {
|
||||
return;
|
||||
}
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
_btnDrawMosaic.selected = drawMosaicEnabled;
|
||||
if(drawMosaicEnabled) {
|
||||
_btnDrawGraffiti.selected = NO;
|
||||
}
|
||||
_drawCtrlView.drawMode = MOSAIC;
|
||||
_drawCtrlView.drawEnable = drawMosaicEnabled;
|
||||
for (TUIMultimediaStickerView *v in _stickerViewList) {
|
||||
v.userInteractionEnabled = !drawMosaicEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<TUIMultimediaSticker *> *)getStickers {
|
||||
NSArray<TUIMultimediaSticker *> *stickers = [_stickerViewList tui_multimedia_map:^TUIMultimediaSticker *(TUIMultimediaStickerView *v) {
|
||||
[v resignFirstResponder];
|
||||
TUIMultimediaSticker *sticker = [[TUIMultimediaSticker alloc] init];
|
||||
sticker.image = [TUIMultimediaImageUtil imageFromView:v withRotate:v.rotation];
|
||||
sticker.frame = v.frame;
|
||||
return sticker;
|
||||
}];
|
||||
|
||||
TUIMultimediaSticker *drawSticker = _drawCtrlView != nil ? _drawCtrlView.drawSticker : nil;
|
||||
if (drawSticker != nil) {
|
||||
stickers = [NSArray tui_multimedia_arrayWithArray:stickers append:@[ drawSticker ]];
|
||||
}
|
||||
return stickers;
|
||||
}
|
||||
|
||||
- (void) clearAllStaticker {
|
||||
if (_drawCtrlView != nil) {
|
||||
[_drawCtrlView clearAllDraw];
|
||||
}
|
||||
|
||||
for (TUIMultimediaStickerView *v in _stickerViewList) {
|
||||
[v removeFromSuperview];
|
||||
}
|
||||
[_stickerViewList removeAllObjects];
|
||||
}
|
||||
|
||||
#pragma mark - UIGestureRecognizer actions
|
||||
- (void)onTapContainerView:(UITapGestureRecognizer *)rec {
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)onBtnSendClicked {
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
[_delegate onCommonEditorControlViewComplete:self stickers:[self getStickers]];
|
||||
}
|
||||
|
||||
- (void)onBtnCancelClicked {
|
||||
[_delegate onCommonEditorControlViewCancel:self];
|
||||
}
|
||||
|
||||
- (void)onBtnSubtitleClicked {
|
||||
[_delegate onCommonEditorControlViewNeedModifySubtitle:[[TUIMultimediaSubtitleInfo alloc] init]
|
||||
callback:^(TUIMultimediaSubtitleInfo *subtitle, BOOL isOk) {
|
||||
if (isOk) {
|
||||
[self addSubtitle:subtitle];
|
||||
}
|
||||
}];
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
}
|
||||
- (void)onBtnPasterClicked {
|
||||
[_delegate onCommonEditorControlViewNeedAddPaster:self];
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
}
|
||||
|
||||
- (void)onBtnDrawGraffitiClicked {
|
||||
[self setDrawEnabled:!_btnDrawGraffiti.selected];
|
||||
_drawCtrlView.drawMode = GRAFFITI;
|
||||
}
|
||||
|
||||
- (void)onBtnDrawMosaicClicked {
|
||||
[self setDrawMosaicEnabled:!_btnDrawMosaic.selected];
|
||||
_drawCtrlView.drawMode = MOSAIC;
|
||||
}
|
||||
|
||||
- (void)onBtnMusicClicked {
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
[_delegate onCommonEditorControlViewNeedEditMusic:self];
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = nil;
|
||||
}
|
||||
|
||||
- (void)onBtnCropClicked {
|
||||
[self setDrawEnabled:NO];
|
||||
[self setDrawMosaicEnabled:NO];
|
||||
[_cropControlView show];
|
||||
_drawCtrlView.drawEnable = NO;
|
||||
_stkViewButtons.hidden = YES;
|
||||
_btnCancel.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)onBtnGenerateCancelClicked {
|
||||
[_delegate onCommonEditorControlViewCancelGenerate:self];
|
||||
self.isGenerating = false;
|
||||
_progressView.progress = 0;
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (BOOL)modifyButtonsHidden {
|
||||
return _stkViewButtons.hidden;
|
||||
}
|
||||
|
||||
- (void)setModifyButtonsHidden:(BOOL)modifyButtonsHidden {
|
||||
_stkViewButtons.hidden = modifyButtonsHidden;
|
||||
}
|
||||
|
||||
- (void)setPreviewSize:(CGSize)previewSize {
|
||||
_previewSize = previewSize;
|
||||
if (previewSize.width == 0 || previewSize.height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int width = self.frame.size.width;
|
||||
int height = previewSize.height / previewSize.width * width;
|
||||
_previewView.frame = CGRectMake(0,0, width, height);
|
||||
_previewView.center = self.center;
|
||||
_previewLimitRect = _previewView.frame;
|
||||
}
|
||||
|
||||
- (BOOL)isGenerating {
|
||||
return !_generateView.hidden;
|
||||
}
|
||||
|
||||
- (void)setIsGenerating:(BOOL)isGenerating {
|
||||
_generateView.hidden = !isGenerating;
|
||||
_stkViewButtons.hidden = isGenerating;
|
||||
_btnCancel.hidden = isGenerating;
|
||||
}
|
||||
|
||||
- (CGFloat)progressBarProgress {
|
||||
return _progressView.progress;
|
||||
}
|
||||
|
||||
- (void)setProgressBarProgress:(CGFloat)progressBarProgress {
|
||||
[_progressView setProgress:progressBarProgress animated:YES];
|
||||
}
|
||||
|
||||
- (BOOL)musicEdited {
|
||||
return _btnMusic != nil ? _btnMusic.selected : FALSE;
|
||||
}
|
||||
|
||||
- (void)setMusicEdited:(BOOL)musicEdited {
|
||||
if (_btnMusic != nil) {
|
||||
_btnMusic.selected = musicEdited;
|
||||
}
|
||||
}
|
||||
|
||||
-(void)setMosaciOriginalImage:(UIImage *)mosaciOriginalImage {
|
||||
_mosaciOriginalImage = mosaciOriginalImage;
|
||||
if (_drawCtrlView) {
|
||||
_drawCtrlView.drawView.mosaciOriginalImage = mosaciOriginalImage;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaCropControlDelegate protocol
|
||||
|
||||
- (void)onCancelCrop {
|
||||
[self previewRotationToZero];
|
||||
self.previewSize = _previewSize;
|
||||
_stkViewButtons.hidden = NO;
|
||||
_btnCancel.hidden = NO;
|
||||
}
|
||||
|
||||
- (void)onConfirmCrop:(CGRect)cropRect {
|
||||
CGFloat cropX = (cropRect.origin.x - _previewView.frame.origin.x) / _previewView.frame.size.width;
|
||||
CGFloat cropY = (cropRect.origin.y - _previewView.frame.origin.y) / _previewView.frame.size.height;
|
||||
CGFloat cropWidth = cropRect.size.width / _previewView.frame.size.width;
|
||||
CGFloat cropHeight = cropRect.size.height / _previewView.frame.size.height;
|
||||
CGRect normalizedCropRect = CGRectMake(cropX, cropY, cropWidth, cropHeight);
|
||||
|
||||
CGFloat rotationAngle = _previewRotationAngle;
|
||||
if ((_previewRotationAngle + 360) % 360 != 0) {
|
||||
_previewView.hidden = YES;
|
||||
}
|
||||
[self previewRotationToZero];
|
||||
|
||||
[_delegate onCommonEditorControlViewCrop:rotationAngle normalizedCropRect:normalizedCropRect stickers:[self getStickers]];
|
||||
_stkViewButtons.hidden = NO;
|
||||
_btnCancel.hidden = NO;
|
||||
[self clearAllStaticker];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaStickerViewDelegate protocol
|
||||
- (void)onStickerViewSelected:(TUIMultimediaStickerView *)v {
|
||||
[_editContainerView bringSubviewToFront:v];
|
||||
_lastSelectedStickerView.selected = NO;
|
||||
_lastSelectedStickerView = v;
|
||||
}
|
||||
|
||||
- (void)onStickerViewShouldDelete:(TUIMultimediaStickerView *)v {
|
||||
[v removeFromSuperview];
|
||||
[_stickerViewList removeObject:v];
|
||||
}
|
||||
|
||||
- (void)onStickerViewShouldEdit:(TUIMultimediaStickerView *)v {
|
||||
if ([v isKindOfClass:TUIMultimediaSubtitleView.class]) {
|
||||
TUIMultimediaSubtitleView *vsub = (TUIMultimediaSubtitleView *)v;
|
||||
vsub.hidden = YES;
|
||||
TUIMultimediaSubtitleInfo *info = vsub.subtitleInfo;
|
||||
[_delegate onCommonEditorControlViewNeedModifySubtitle:info
|
||||
callback:^(TUIMultimediaSubtitleInfo *newInfo, BOOL isOk) {
|
||||
if (isOk) {
|
||||
vsub.subtitleInfo = newInfo;
|
||||
}
|
||||
vsub.hidden = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onStickerViewSizeChanged:(TUIMultimediaStickerView *)v {
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaDrawCtrlViewDelegate
|
||||
- (void)onIsDrawCtrlViewDrawing:(BOOL)Hidden {
|
||||
_stkViewButtons.hidden = Hidden;
|
||||
}
|
||||
@end
|
||||
|
||||
#pragma mark - TUIMultimediaCommonEditorConfig
|
||||
@implementation TUIMultimediaCommonEditorConfig
|
||||
+ (instancetype)configForVideoEditor {
|
||||
TUIMultimediaCommonEditorConfig *config = [[TUIMultimediaCommonEditorConfig alloc] init];
|
||||
config.pasterEnabled = [[TUIMultimediaConfig sharedInstance] isSupportVideoEditPaster];
|
||||
config.subtitleEnabled = [[TUIMultimediaConfig sharedInstance] isSupportVideoEditSubtitle];
|
||||
config.drawGraffitiEnabled = [[TUIMultimediaConfig sharedInstance] isSupportVideoEditGraffiti];
|
||||
config.musicEditEnabled = [[TUIMultimediaConfig sharedInstance] isSupportVideoEditBGM];
|
||||
config.cropEnabled = NO;
|
||||
config.drawMosaicEnabled = NO;
|
||||
return config;
|
||||
}
|
||||
+ (instancetype)configForPictureEditor {
|
||||
TUIMultimediaCommonEditorConfig *config = [[TUIMultimediaCommonEditorConfig alloc] init];
|
||||
config.pasterEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditPaster];
|
||||
config.subtitleEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditSubtitle];
|
||||
config.drawGraffitiEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditGraffiti];
|
||||
config.drawMosaicEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditMosaic];
|
||||
config.cropEnabled = [[TUIMultimediaConfig sharedInstance] isSupportPictureEditCrop];
|
||||
config.musicEditEnabled = NO;
|
||||
return config;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaCropView.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMultimediaCommonEditorControlView;
|
||||
@protocol TUIMultimediaCropControlDelegate;
|
||||
|
||||
@interface TUIMultimediaCropControlView : UIView
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaCropControlDelegate> delegate;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame editorControl:(TUIMultimediaCommonEditorControlView*)editorControl;
|
||||
- (void)show;
|
||||
- (void)changeResetButtonStatus;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaCropControlDelegate <NSObject>
|
||||
- (void)onCancelCrop;
|
||||
- (void)onConfirmCrop:(CGRect)cropRect;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaCropControlView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
|
||||
#import "TUIMultimediaCropView.h"
|
||||
#import "TUIMultimediaCommonEditorControlView.h"
|
||||
|
||||
@interface TUIMultimediaCropControlView()<TUIMultimediaCropDelegate> {
|
||||
TUIMultimediaCommonEditorControlView* _editorControl;
|
||||
TUIMultimediaCropView *_cropView;
|
||||
UIButton* _restoreButton;
|
||||
UIButton* _rotationButton;
|
||||
UIButton* _confirmButton;
|
||||
UIButton* _cancelButton;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaCropControlView
|
||||
- (instancetype)initWithFrame:(CGRect)frame editorControl:(TUIMultimediaCommonEditorControlView*)editorControl{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_editorControl = editorControl;
|
||||
[self initUI];
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
#pragma mark - UI init
|
||||
|
||||
- (void)initUI {
|
||||
_cropView = [[TUIMultimediaCropView alloc] initWithFrame:self.frame];
|
||||
[self addSubview:_cropView];
|
||||
[_cropView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.size.equalTo(self);
|
||||
}];
|
||||
_cropView.delegate = self;
|
||||
|
||||
_restoreButton = [self addFuncitonButtonWithText:[TUIMultimediaCommon localizedStringForKey:@"restore"]
|
||||
action:@selector(onResetBtnClicked) bottomOffset:-75 leftOffset:7.5 rightOffset:0 size:CGSizeMake(80, 22)];
|
||||
_cancelButton = [self addFunctionButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_crop_cancel", @"crop_cancel")
|
||||
action:@selector(onCancelBtnClicked) bottomOffset:-25 leftOffset:35 rightOffset:0 size:CGSizeMake(25, 25)];
|
||||
_confirmButton = [self addFunctionButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_crop_confirm", @"crop_ok")
|
||||
action:@selector(onConfirmBtnClicked) bottomOffset:-25 leftOffset:0 rightOffset:-32.5 size:CGSizeMake(30, 28)];
|
||||
_rotationButton = [self addFunctionButtonWithImage:TUIMultimediaPluginBundleThemeImage(@"editor_crop_rotation", @"crop_rotation")
|
||||
action:@selector(onRotationBtnClicked) bottomOffset:-75 leftOffset:0 rightOffset:-35 size:CGSizeMake(25, 25)];
|
||||
|
||||
_restoreButton.enabled = NO;
|
||||
}
|
||||
|
||||
-(void)show {
|
||||
self.hidden = NO;
|
||||
_cropView.preViewFrame = _editorControl.previewView.frame;
|
||||
[_cropView reset];
|
||||
_restoreButton.enabled = NO;
|
||||
}
|
||||
|
||||
- (void)changeResetButtonStatus {
|
||||
_restoreButton.enabled = ![self isApproximateSize:[_cropView getCropRect].size size2:_editorControl.previewView.frame.size];
|
||||
}
|
||||
|
||||
-(void)onResetBtnClicked {
|
||||
[_editorControl previewRotationToZero];
|
||||
_cropView.preViewFrame = _editorControl.previewView.frame;
|
||||
[_cropView reset];
|
||||
_restoreButton.enabled = NO;
|
||||
}
|
||||
|
||||
-(void)onRotationBtnClicked {
|
||||
CGRect cropRect = [_cropView getCropRect];
|
||||
CGPoint rotationCenter = CGPointMake(CGRectGetMidX(cropRect), CGRectGetMidY(cropRect));
|
||||
[_editorControl previewRotation90:rotationCenter];
|
||||
[_cropView rotation90];
|
||||
_restoreButton.enabled = YES;
|
||||
}
|
||||
|
||||
-(void)onCancelBtnClicked {
|
||||
self.hidden = YES;
|
||||
if (_delegate != nil) {
|
||||
[_delegate onCancelCrop];
|
||||
}
|
||||
}
|
||||
|
||||
-(void)onConfirmBtnClicked {
|
||||
self.hidden = YES;
|
||||
if (_delegate != nil) {
|
||||
if (_restoreButton.isEnabled) {
|
||||
[_delegate onConfirmCrop:[_cropView getCropRect]];
|
||||
} else {
|
||||
[_delegate onCancelCrop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(UIButton*) addFuncitonButtonWithText:(NSString*)text action:(SEL)actionSel bottomOffset:(int)bottomOffset
|
||||
leftOffset:(int)leftOffset rightOffset:(int)rightOffset size:(CGSize)size {
|
||||
UIButton* button = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[self addSubview:button];
|
||||
button.backgroundColor = [UIColor clearColor];
|
||||
[button mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
if (leftOffset > 0) {
|
||||
make.leading.equalTo(self).offset(leftOffset);
|
||||
}
|
||||
|
||||
if (rightOffset < 0) {
|
||||
make.trailing.equalTo(self).offset(rightOffset);
|
||||
}
|
||||
|
||||
make.bottom.equalTo(self).offset(bottomOffset);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
[button addTarget:self action:actionSel forControlEvents:UIControlEventTouchUpInside];
|
||||
[button setTitle:text forState:UIControlStateNormal];
|
||||
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
[button setTitleColor:[UIColor grayColor] forState:UIControlStateDisabled];
|
||||
return button;
|
||||
}
|
||||
|
||||
- (UIButton *)addFunctionButtonWithImage:(UIImage *)image action:(SEL)actionSel bottomOffset:(int)bottomOffset
|
||||
leftOffset:(int)leftOffset rightOffset:(int)rightOffset size:(CGSize)size{
|
||||
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[self addSubview:button];
|
||||
UIImage *imgNormal = [TUIMultimediaImageUtil imageFromImage:image withTintColor:
|
||||
TUIMultimediaPluginDynamicColor(@"editor_func_btn_normal_color", @"#FFFFFF")];
|
||||
[button setImage:imgNormal forState:UIControlStateNormal];
|
||||
[button setImageEdgeInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
|
||||
[button addTarget:self action:actionSel forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[button mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
if (leftOffset > 0) {
|
||||
make.leading.equalTo(self).offset(leftOffset);
|
||||
}
|
||||
|
||||
if (rightOffset < 0) {
|
||||
make.trailing.equalTo(self).offset(rightOffset);
|
||||
}
|
||||
|
||||
make.bottom.equalTo(self).offset(bottomOffset);
|
||||
make.size.mas_equalTo(size);
|
||||
}];
|
||||
return button;
|
||||
}
|
||||
|
||||
|
||||
-(BOOL)isApproximateSize:(CGSize)size1 size2:(CGSize)size2 {
|
||||
return ABS(size1.width - size1.width ) < 1 && ABS(size1.height - size2.height) < 1;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaCropViewDelegate
|
||||
- (void)onStartCrop {
|
||||
_editorControl.isStartCrop = YES;
|
||||
_restoreButton.enabled = YES;
|
||||
}
|
||||
|
||||
- (void)onCropComplete:(CGFloat)scale centerPoint:(CGPoint)centerPoint offset:(CGPoint)offset {
|
||||
_editorControl.isStartCrop = NO;
|
||||
_editorControl.previewLimitRect = [_cropView getCropRect];
|
||||
[_editorControl previewScale:scale center:centerPoint];
|
||||
[_editorControl previewMove:offset];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol TUIMultimediaCropDelegate;
|
||||
|
||||
@interface TUIMultimediaCropView : UIView
|
||||
@property(nonatomic) CGRect preViewFrame;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaCropDelegate> delegate;
|
||||
-(void)reset;
|
||||
-(void)rotation90;
|
||||
-(CGRect)getCropRect;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaCropDelegate <NSObject>
|
||||
- (void)onStartCrop;
|
||||
- (void)onCropComplete:(CGFloat)scale centerPoint:(CGPoint)centerPoint offset:(CGPoint)offset;
|
||||
@end
|
||||
@@ -0,0 +1,334 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaCropView.h"
|
||||
|
||||
#import <ReactiveObjC/ReactiveObjC.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
#define TOUCH_MAX_DIS 40
|
||||
#define CORNER_LINE_WIDTH 4.0
|
||||
#define CORNER_LENGTH 25.0
|
||||
#define CROP_RECT_BORDER_LINE_WIDTH 2.0
|
||||
#define CROP_RECT_GRID_LINE_WIDTH 0.8
|
||||
|
||||
@interface TUIMultimediaCropView()<UIGestureRecognizerDelegate, UIGestureRecognizerDelegate>{
|
||||
UIColor * _borderColor;
|
||||
UIColor* _backgroundColor;
|
||||
|
||||
NSDate * _lastShowTransparentBackgroundTime;
|
||||
NSDate * _lastShowGridTime;
|
||||
BOOL _isShowTransparentBackground;
|
||||
BOOL _isShowGird;
|
||||
|
||||
CGRect _limitRect;
|
||||
CGRect _cropRect;
|
||||
|
||||
CGFloat _isMoveLeft;
|
||||
CGFloat _isMoveRight;
|
||||
CGFloat _isMoveTop;
|
||||
CGFloat _isMoveBottom;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaCropView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
_borderColor = [UIColor whiteColor];
|
||||
_backgroundColor = [UIColor blackColor];
|
||||
_isShowTransparentBackground = NO;
|
||||
_isShowGird = NO;
|
||||
|
||||
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
|
||||
panGesture.maximumNumberOfTouches = 1;
|
||||
panGesture.delegate = self;
|
||||
[self addGestureRecognizer:panGesture];
|
||||
|
||||
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
|
||||
[self addGestureRecognizer:tapGesture];
|
||||
|
||||
UIPinchGestureRecognizer *pinchRec = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(onPinch:)];
|
||||
[self addGestureRecognizer:pinchRec];
|
||||
pinchRec.delegate = self;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(CGRect)getCropRect {
|
||||
return _cropRect;
|
||||
}
|
||||
|
||||
-(void)reset {
|
||||
[self adjustCropRect:_preViewFrame];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
-(void)rotation90 {
|
||||
int centerX = CGRectGetMidX(_cropRect);
|
||||
int centerY = CGRectGetMidY(_cropRect);
|
||||
int width = _cropRect.size.width;
|
||||
int height = _cropRect.size.height;
|
||||
|
||||
_cropRect.origin.y = centerY - width / 2.0f;
|
||||
_cropRect.size.height = width;
|
||||
|
||||
_cropRect.origin.x = centerX - height / 2.0f;
|
||||
_cropRect.size.width = height;
|
||||
|
||||
[self adjustCropRect:_cropRect];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_limitRect = CGRectMake(10, 50, self.frame.size.width - 20, self.frame.size.height - 160);
|
||||
_cropRect = _limitRect;
|
||||
}
|
||||
|
||||
- (void)onTap:(UITapGestureRecognizer *)gesture {
|
||||
[self showTransparentBackground];
|
||||
[self showGrid];
|
||||
}
|
||||
|
||||
- (void)onPinch:(UIPinchGestureRecognizer *)gestureRecognizer {
|
||||
[self showTransparentBackground];
|
||||
[self showGrid];
|
||||
}
|
||||
|
||||
- (void)onPan:(UIPanGestureRecognizer *)gesture {
|
||||
_backgroundColor = [UIColor clearColor];
|
||||
[self showTransparentBackground];
|
||||
[self showGrid];
|
||||
switch (gesture.state) {
|
||||
case UIGestureRecognizerStateBegan: {
|
||||
CGPoint p = [gesture locationInView:self];
|
||||
_isMoveLeft = fabs(p.x - _cropRect.origin.x) < TOUCH_MAX_DIS;
|
||||
_isMoveRight = fabs(p.x - CGRectGetMaxX(_cropRect)) < TOUCH_MAX_DIS;
|
||||
_isMoveTop = fabs(p.y - _cropRect.origin.y) < TOUCH_MAX_DIS;
|
||||
_isMoveBottom = fabs(p.y - CGRectGetMaxY(_cropRect)) < TOUCH_MAX_DIS;
|
||||
if (_isMoveTop || _isMoveLeft || _isMoveRight || _isMoveBottom) {
|
||||
[_delegate onStartCrop];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
CGPoint p = [gesture locationInView:self];
|
||||
if (_isMoveLeft && p.x > _limitRect.origin.x && p.x < CGRectGetMaxX(_cropRect)) {
|
||||
_cropRect.size.width += (_cropRect.origin.x - p.x);
|
||||
_cropRect.origin.x = p.x;
|
||||
}
|
||||
|
||||
if (_isMoveRight && p.x > _cropRect.origin.x && p.x < CGRectGetMaxX(_limitRect)) {
|
||||
_cropRect.size.width = p.x - _cropRect.origin.x;
|
||||
}
|
||||
|
||||
if (_isMoveTop && p.y > _limitRect.origin.y && p.y < CGRectGetMaxY(_cropRect)) {
|
||||
_cropRect.size.height += (_cropRect.origin.y - p.y);
|
||||
_cropRect.origin.y = p.y;
|
||||
}
|
||||
|
||||
if (_isMoveBottom && p.y > _cropRect.origin.y && p.y < CGRectGetMaxY(_limitRect)) {
|
||||
_cropRect.size.height = p.y - _cropRect.origin.y;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateEnded: {
|
||||
if (_isMoveTop || _isMoveLeft || _isMoveRight || _isMoveBottom) {
|
||||
[self adjustCropRect:_cropRect];
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
[gesture setTranslation:CGPointZero inView:self.superview];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
|
||||
- (void) adjustCropRect:(CGRect) sourceCropRect {
|
||||
if (CGRectIsEmpty(sourceCropRect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
float limitRectRation = _limitRect.size.width * 1.0f / _limitRect.size.height;
|
||||
float sourceRectRation = sourceCropRect.size.width * 1.0f / sourceCropRect.size.height;
|
||||
|
||||
if (sourceRectRation > limitRectRation) {
|
||||
_cropRect.origin.x = _limitRect.origin.x;
|
||||
_cropRect.size.width = _limitRect.size.width;
|
||||
_cropRect.size.height = _cropRect.size.width / sourceRectRation;
|
||||
_cropRect.origin.y = (_limitRect.size.height - _cropRect.size.height) / 2.0f + _limitRect.origin.y;
|
||||
} else {
|
||||
_cropRect.origin.y = _limitRect.origin.y;
|
||||
_cropRect.size.height = _limitRect.size.height;
|
||||
_cropRect.size.width = _cropRect.size.height * sourceRectRation;
|
||||
_cropRect.origin.x = (_limitRect.size.width - _cropRect.size.width) / 2.0f + _limitRect.origin.x;
|
||||
}
|
||||
|
||||
CGFloat scale = _cropRect.size.width * 1.0f / sourceCropRect.size.width;
|
||||
CGFloat moveX = _cropRect.origin.x - sourceCropRect.origin.x;
|
||||
CGFloat moveY = _cropRect.origin.y - sourceCropRect.origin.y;
|
||||
|
||||
[_delegate onCropComplete:scale centerPoint:sourceCropRect.origin
|
||||
offset:CGPointMake(moveX, moveY)];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
[super drawRect:rect];
|
||||
|
||||
if (_cropRect.size.width == 0 || _cropRect.size.height == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (_isShowTransparentBackground) {
|
||||
[[UIColor clearColor] setFill];
|
||||
CGContextFillRect(context, rect);
|
||||
CGContextFillPath(context);
|
||||
} else {
|
||||
[[UIColor blackColor] setFill];
|
||||
CGContextFillRect(context, rect);
|
||||
CGContextAddRect(context, _cropRect);
|
||||
CGContextClip(context);
|
||||
CGContextSetBlendMode(context, kCGBlendModeClear);
|
||||
CGContextAddRect(context, _cropRect);
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
|
||||
CGContextSetBlendMode(context, kCGBlendModeNormal);
|
||||
[self drawCropRect:context];
|
||||
[self drawCorner:context];
|
||||
if (_isShowGird) {
|
||||
[self drawGrid:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) drawCropRect:(CGContextRef) context {
|
||||
CGContextSetStrokeColorWithColor(context, _borderColor.CGColor);
|
||||
CGContextSetLineWidth(context, CROP_RECT_BORDER_LINE_WIDTH );
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
||||
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void) drawCorner:(CGContextRef) context {
|
||||
float corner_width = CORNER_LINE_WIDTH;
|
||||
if (!_isShowTransparentBackground) {
|
||||
corner_width = 1.5f * corner_width;
|
||||
}
|
||||
CGContextSetLineWidth(context, corner_width);
|
||||
float halfLineWidth = corner_width / 2.0f;
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) - halfLineWidth, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) - halfLineWidth);
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect) + halfLineWidth, CGRectGetMinY(_cropRect));
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) - halfLineWidth);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + CORNER_LENGTH);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMaxY(_cropRect) + halfLineWidth);
|
||||
CGContextMoveToPoint(context, CGRectGetMaxX(_cropRect) - CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect) + halfLineWidth, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMaxY(_cropRect) - CORNER_LENGTH);
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMaxY(_cropRect) + halfLineWidth);
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) - halfLineWidth, CGRectGetMaxY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + CORNER_LENGTH, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void) drawGrid:(CGContextRef) context {
|
||||
CGContextSetLineWidth(context, CROP_RECT_GRID_LINE_WIDTH);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width / 3, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width / 3, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width * 2 / 3, CGRectGetMinY(_cropRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(_cropRect) + _cropRect.size.width * 2 / 3, CGRectGetMaxY(_cropRect));
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height / 3);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height / 3);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height * 2 / 3);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(_cropRect), CGRectGetMinY(_cropRect) + _cropRect.size.height * 2 / 3);
|
||||
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) showTransparentBackground {
|
||||
_lastShowTransparentBackgroundTime = [NSDate date];
|
||||
if (!_isShowTransparentBackground) {
|
||||
_isShowTransparentBackground = YES;
|
||||
[self setNeedsDisplay];
|
||||
[self delayCancelShowTransparentBackground];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) delayCancelShowTransparentBackground {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC));
|
||||
|
||||
@weakify(self)
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
||||
@strongify(self)
|
||||
if ([[NSDate date] timeIntervalSinceDate:self->_lastShowTransparentBackgroundTime] < 2.0f) {
|
||||
[self delayCancelShowTransparentBackground];
|
||||
return;
|
||||
}
|
||||
self->_isShowTransparentBackground = NO;
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) showGrid {
|
||||
_lastShowGridTime = [NSDate date];
|
||||
if (!_isShowGird) {
|
||||
_isShowGird = YES;
|
||||
[self setNeedsDisplay];
|
||||
[self delayCancelShowGrid];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) delayCancelShowGrid {
|
||||
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC));
|
||||
|
||||
@weakify(self)
|
||||
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
||||
@strongify(self)
|
||||
if ([[NSDate date] timeIntervalSinceDate:self->_lastShowGridTime] < 4.0f) {
|
||||
[self delayCancelShowGrid];
|
||||
return;
|
||||
}
|
||||
self->_isShowGird = NO;
|
||||
[self setNeedsDisplay];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@class TUIMultimediaDrawView;
|
||||
@class TUIMultimediaSticker;
|
||||
@protocol TUIMultimediaDrawCtrlViewDelegate;
|
||||
enum DrawMode:NSInteger;
|
||||
|
||||
@interface TUIMultimediaDrawCtrlView : UIView
|
||||
-(void)flushRedoUndoState;
|
||||
-(void)clearAllDraw;
|
||||
|
||||
@property (nonatomic, strong) TUIMultimediaDrawView* drawView;
|
||||
@property (nonatomic) enum DrawMode drawMode;
|
||||
@property (nonatomic) BOOL drawEnable;
|
||||
@property (nonatomic, strong, readonly) TUIMultimediaSticker * drawSticker;
|
||||
@property (weak, nullable, nonatomic) id<TUIMultimediaDrawCtrlViewDelegate> delegate;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaDrawCtrlViewDelegate <NSObject>
|
||||
- (void)onIsDrawCtrlViewDrawing:(BOOL)Hidden;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaDrawCtrlView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSplitter.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageUtil.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSticker.h"
|
||||
|
||||
#define FunctionButtonColorNormal TUIMultimediaPluginDynamicColor(@"editor_func_btn_normal_color", @"#FFFFFF")
|
||||
#define FunctionButtonColorDisabled TUIMultimediaPluginDynamicColor(@"editor_func_btn_disabled_color", @"#6D6D6D")
|
||||
|
||||
@interface TUIMultimediaDrawCtrlView () <TUIMultimediaColorPanelDelegate, TUIMultimediaDrawViewDelegate> {
|
||||
TUIMultimediaColorPanel *_colorPanel;
|
||||
UIButton *_btnRedo;
|
||||
UIButton *_btnUndo;
|
||||
TUIMultimediaSplitter *_vertical_splitter;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaDrawCtrlView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
[self initCtrlView];
|
||||
[self initDrawView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initCtrlView {
|
||||
self.backgroundColor = TUIMultimediaPluginDynamicColor(@"editor_draw_panel_bg_color", @"#3333337F");
|
||||
|
||||
_colorPanel = [[TUIMultimediaColorPanel alloc] init];
|
||||
_colorPanel.delegate = self;
|
||||
[self addSubview:_colorPanel];
|
||||
|
||||
UIImage *imgUndo = TUIMultimediaPluginBundleThemeImage(@"editor_undo_img", @"undo");
|
||||
_btnUndo = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[self addSubview:_btnUndo];
|
||||
_btnUndo.enabled = NO;
|
||||
[_btnUndo addTarget:self action:@selector(onBtnUndoClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnUndo setImage:[TUIMultimediaImageUtil imageFromImage:imgUndo withTintColor:FunctionButtonColorNormal] forState:UIControlStateNormal];
|
||||
[_btnUndo setImage:[TUIMultimediaImageUtil imageFromImage:imgUndo withTintColor:FunctionButtonColorDisabled] forState:UIControlStateDisabled];
|
||||
|
||||
UIImage *imgRedo = TUIMultimediaPluginBundleThemeImage(@"editor_redo_img", @"redo");
|
||||
_btnRedo = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[self addSubview:_btnRedo];
|
||||
_btnRedo.enabled = NO;
|
||||
[_btnRedo addTarget:self action:@selector(onBtnRedoClicked) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_btnRedo setImage:[TUIMultimediaImageUtil imageFromImage:imgRedo withTintColor:FunctionButtonColorNormal] forState:UIControlStateNormal];
|
||||
[_btnRedo setImage:[TUIMultimediaImageUtil imageFromImage:imgRedo withTintColor:FunctionButtonColorDisabled] forState:UIControlStateDisabled];
|
||||
|
||||
TUIMultimediaSplitter * horizontal_splitter = [[TUIMultimediaSplitter alloc] init];
|
||||
[self addSubview:horizontal_splitter];
|
||||
horizontal_splitter.axis = UILayoutConstraintAxisHorizontal;
|
||||
|
||||
_vertical_splitter = [[TUIMultimediaSplitter alloc] init];
|
||||
[self addSubview:_vertical_splitter];
|
||||
_vertical_splitter.axis = UILayoutConstraintAxisVertical;
|
||||
|
||||
[_btnRedo mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self).inset(5);
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
[_btnUndo mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.right.equalTo(_btnRedo.mas_left).inset(20);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
[_colorPanel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.left.equalTo(self).inset(5);
|
||||
make.right.equalTo(_btnUndo.mas_left).inset(10);
|
||||
make.height.mas_equalTo(32);
|
||||
}];
|
||||
[horizontal_splitter mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self).inset(3);
|
||||
make.top.equalTo(_colorPanel.mas_bottom).inset(5);
|
||||
make.height.mas_equalTo(5);
|
||||
}];
|
||||
[_vertical_splitter mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self).inset(3);
|
||||
make.bottom.equalTo(horizontal_splitter.mas_top).inset(3);
|
||||
make.left.equalTo(_colorPanel.mas_right).inset(3);
|
||||
make.width.mas_equalTo(5);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)initDrawView {
|
||||
_drawView = [[TUIMultimediaDrawView alloc] init];
|
||||
_drawView.userInteractionEnabled = NO;
|
||||
UITapGestureRecognizer *drawTapRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapDrawView:)];
|
||||
[_drawView addGestureRecognizer:drawTapRec];
|
||||
_drawView.delegate = self;
|
||||
}
|
||||
|
||||
- (void) setDrawEnable:(BOOL)drawEnable {
|
||||
_drawView.userInteractionEnabled = drawEnable;
|
||||
self.hidden = !drawEnable;
|
||||
}
|
||||
|
||||
- (void) setDrawMode:(enum DrawMode)drawMode {
|
||||
_drawMode = drawMode;
|
||||
_drawView.drawMode = drawMode;
|
||||
[self updateUIAccordingToDrawMode];
|
||||
[self flushRedoUndoState];
|
||||
}
|
||||
|
||||
- (TUIMultimediaSticker *)drawSticker {
|
||||
if (_drawView != nil && _drawView.pathCount > 0) {
|
||||
TUIMultimediaSticker *drawSticker = [[TUIMultimediaSticker alloc] init];
|
||||
drawSticker.image = [TUIMultimediaImageUtil imageFromView:_drawView];
|
||||
drawSticker.frame = _drawView.frame;
|
||||
return drawSticker;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)onTapDrawView:(UITapGestureRecognizer *)rec {
|
||||
self.hidden = !self.hidden;
|
||||
[self.delegate onIsDrawCtrlViewDrawing:self.hidden];
|
||||
}
|
||||
|
||||
- (void)flushRedoUndoState {
|
||||
_btnUndo.enabled = _drawView.canUndo;
|
||||
_btnRedo.enabled = _drawView.canRedo;
|
||||
}
|
||||
|
||||
-(void)clearAllDraw {
|
||||
[_drawView clear];
|
||||
}
|
||||
|
||||
-(void) updateUIAccordingToDrawMode{
|
||||
if (_drawMode == GRAFFITI) {
|
||||
_colorPanel.hidden = NO;
|
||||
_vertical_splitter.hidden = NO;
|
||||
|
||||
[_btnRedo mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self).inset(5);
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
[_btnUndo mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.right.equalTo(_btnRedo.mas_left).inset(20);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
|
||||
} else {
|
||||
_colorPanel.hidden = YES;
|
||||
_vertical_splitter.hidden = YES;
|
||||
|
||||
[_btnUndo mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.centerX.equalTo(self.mas_trailing).multipliedBy(0.25);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
|
||||
[_btnRedo mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(_colorPanel);
|
||||
make.centerX.equalTo(self.mas_trailing).multipliedBy(0.75);
|
||||
make.size.mas_equalTo(CGSizeMake(30, 30));
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaColorPanelDelegate protocol
|
||||
- (void)onColorPanel:(TUIMultimediaColorPanel *)panel selectColor:(UIColor *)color {
|
||||
_drawView.color = color;
|
||||
}
|
||||
|
||||
- (void)onBtnUndoClicked {
|
||||
[_drawView undo];
|
||||
[self flushRedoUndoState];
|
||||
}
|
||||
|
||||
- (void)onBtnRedoClicked {
|
||||
[_drawView redo];
|
||||
[self flushRedoUndoState];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaDrawViewDelegate
|
||||
- (void)drawViewPathListChanged:(TUIMultimediaDrawView *)v {
|
||||
[self flushRedoUndoState];
|
||||
}
|
||||
|
||||
- (void)drawViewDrawStarted:(TUIMultimediaDrawView *)v {
|
||||
[self.delegate onIsDrawCtrlViewDrawing:YES];
|
||||
self.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)drawViewDrawEnded:(TUIMultimediaDrawView *)v {
|
||||
[self.delegate onIsDrawCtrlViewDrawing:NO];
|
||||
self.hidden = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TUIMultimediaPath;
|
||||
@protocol TUIMultimediaDrawViewDelegate;
|
||||
|
||||
typedef NS_ENUM(NSInteger, DrawMode){
|
||||
GRAFFITI,
|
||||
MOSAIC
|
||||
};
|
||||
|
||||
@interface TUIMultimediaDrawView : UIView
|
||||
@property(nonatomic) UIColor *color;
|
||||
@property(nonatomic) CGFloat lineWidth;
|
||||
@property(readonly, nonatomic) NSInteger pathCount;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaDrawViewDelegate> delegate;
|
||||
@property(readonly, nonatomic) BOOL canUndo;
|
||||
@property(readonly, nonatomic) BOOL canRedo;
|
||||
@property(nonatomic) DrawMode drawMode;
|
||||
@property (nonatomic, strong) UIImage *mosaciOriginalImage;
|
||||
|
||||
- (void)clear;
|
||||
- (void)undo;
|
||||
- (void)redo;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaDrawViewDelegate <NSObject>
|
||||
- (void)drawViewPathListChanged:(TUIMultimediaDrawView *)v;
|
||||
- (void)drawViewDrawStarted:(TUIMultimediaDrawView *)v;
|
||||
- (void)drawViewDrawEnded:(TUIMultimediaDrawView *)v;
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIMultimediaPath : NSObject
|
||||
@property(nonatomic) CGSize originCanvasSize;
|
||||
@property(nonatomic, nonnull) NSMutableArray<NSValue *> *pathPoints;
|
||||
@property(nonatomic, nonnull) UIColor *color;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaDrawView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaMosaicDrawView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGraffitiDrawView.h"
|
||||
|
||||
@interface TUIMultimediaDrawView () {
|
||||
TUIMultimediaMosaicDrawView* _mosaciDrawView;
|
||||
TUIMultimediaGraffitiDrawView* _graffitiDrawView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaDrawView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
|
||||
panRec.maximumNumberOfTouches = 1;
|
||||
[self addGestureRecognizer:panRec];
|
||||
|
||||
_mosaciDrawView = [[TUIMultimediaMosaicDrawView alloc] initWithFrame:self.bounds];
|
||||
[self addSubview:_mosaciDrawView];
|
||||
[_mosaciDrawView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
_graffitiDrawView = [[TUIMultimediaGraffitiDrawView alloc] initWithFrame:self.bounds];
|
||||
[self addSubview:_graffitiDrawView];
|
||||
[_graffitiDrawView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canUndo {
|
||||
return _drawMode == GRAFFITI ? _graffitiDrawView.canUndo : _mosaciDrawView.canUndo;
|
||||
}
|
||||
|
||||
- (BOOL)canRedo {
|
||||
return _drawMode == GRAFFITI ? _graffitiDrawView.canRedo : _mosaciDrawView.canRedo;
|
||||
}
|
||||
|
||||
- (void)clear {
|
||||
[_graffitiDrawView clearGraffiti];
|
||||
[_mosaciDrawView clearMosaic];
|
||||
}
|
||||
|
||||
- (void)undo {
|
||||
if (_drawMode == GRAFFITI) {
|
||||
[_graffitiDrawView undo];
|
||||
} else {
|
||||
[_mosaciDrawView undo];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)redo {
|
||||
if (_drawMode == GRAFFITI) {
|
||||
[_graffitiDrawView redo];
|
||||
} else {
|
||||
[_mosaciDrawView redo];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onPan:(UIPanGestureRecognizer *)rec {
|
||||
switch (rec.state) {
|
||||
case UIGestureRecognizerStateBegan:
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
CGPoint p = [rec locationInView:self];
|
||||
if (_drawMode == MOSAIC) {
|
||||
[_mosaciDrawView addPathPoint:p];
|
||||
} else {
|
||||
[_graffitiDrawView addPathPoint:p];
|
||||
}
|
||||
[_delegate drawViewDrawStarted:self];
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateEnded: {
|
||||
if (_drawMode == MOSAIC) {
|
||||
[_mosaciDrawView completeAddPoint];
|
||||
} else {
|
||||
[_graffitiDrawView completeAddPoint];
|
||||
}
|
||||
[_delegate drawViewDrawEnded:self];
|
||||
[_delegate drawViewPathListChanged:self];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger) pathCount {
|
||||
return _mosaciDrawView.pathCount + _graffitiDrawView.pathCount;
|
||||
}
|
||||
|
||||
-(void) setMosaciOriginalImage:(UIImage *)mosaciOriginalImage {
|
||||
_mosaciDrawView.originalImage = mosaciOriginalImage;
|
||||
}
|
||||
|
||||
-(void) setColor:(UIColor *)color {
|
||||
_graffitiDrawView.color = color;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIMultimediaGraffitiDrawView : UIView
|
||||
@property(nonatomic) UIColor *color;
|
||||
@property(readonly, nonatomic) BOOL canUndo;
|
||||
@property(readonly, nonatomic) BOOL canRedo;
|
||||
@property(readonly, nonatomic) NSInteger pathCount;
|
||||
|
||||
- (void)clearGraffiti;
|
||||
- (void)undo;
|
||||
- (void)redo;
|
||||
- (void)addPathPoint:(CGPoint) point;
|
||||
- (void)completeAddPoint;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaGraffitiDrawView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawView.h"
|
||||
|
||||
#define GRAFFITI_LINE_WIDTH 4
|
||||
#define CGPointMultiply(point, scalar) CGPointMake(point.x * scalar, point.y * scalar)
|
||||
|
||||
@interface TUIMultimediaGraffitiDrawView () {
|
||||
NSMutableArray<NSValue *> *_currentPathPoints;
|
||||
UIBezierPath *_currentPath;
|
||||
NSMutableArray<TUIMultimediaPath *> *_pathList;
|
||||
NSMutableArray<TUIMultimediaPath *> *_redoPathList;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaGraffitiDrawView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
_pathList = [NSMutableArray array];
|
||||
_redoPathList = [NSMutableArray array];
|
||||
_currentPathPoints = [NSMutableArray array];
|
||||
_color = UIColor.whiteColor;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger) pathCount {
|
||||
return _pathList.count;
|
||||
}
|
||||
|
||||
- (BOOL)canUndo {
|
||||
return _pathList.count != 0;
|
||||
}
|
||||
|
||||
- (BOOL)canRedo {
|
||||
return _redoPathList.count != 0;
|
||||
}
|
||||
|
||||
- (void)clearGraffiti {
|
||||
[_pathList removeAllObjects];
|
||||
[_redoPathList removeAllObjects];
|
||||
[_currentPath removeAllPoints];
|
||||
[_currentPathPoints removeAllObjects];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)undo {
|
||||
if (_pathList.count == 0) {
|
||||
return;
|
||||
}
|
||||
TUIMultimediaPath *path = [_pathList lastObject];
|
||||
[_pathList removeLastObject];
|
||||
[_redoPathList addObject:path];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)redo {
|
||||
if (_redoPathList.count == 0) {
|
||||
return;
|
||||
}
|
||||
TUIMultimediaPath *path = [_redoPathList lastObject];
|
||||
[_redoPathList removeLastObject];
|
||||
[_pathList addObject:path];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
[super drawRect:rect];
|
||||
|
||||
[[UIColor clearColor] setFill];
|
||||
UIRectFill(rect);
|
||||
|
||||
for (TUIMultimediaPath *path in _pathList) {
|
||||
[path.color set];
|
||||
CGFloat scale = self.frame.size.width / path.originCanvasSize.width;
|
||||
UIBezierPath * bezierPath = [TUIMultimediaGraffitiDrawView smoothBezierPathFromPoints:path.pathPoints scale:scale];
|
||||
[bezierPath stroke];
|
||||
}
|
||||
|
||||
if (_currentPath != nil) {
|
||||
[_color set];
|
||||
[_currentPath stroke];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<TUIMultimediaPath *> *)pathList {
|
||||
return _pathList;
|
||||
}
|
||||
|
||||
- (void)addPathPoint:(CGPoint) point {
|
||||
[_currentPathPoints addObject:@(point)];
|
||||
_currentPath = [TUIMultimediaGraffitiDrawView smoothBezierPathFromPoints:_currentPathPoints scale:1.0];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)completeAddPoint {
|
||||
TUIMultimediaPath *path = [[TUIMultimediaPath alloc] init];
|
||||
path.pathPoints = [[NSMutableArray alloc] initWithArray:_currentPathPoints copyItems:YES];
|
||||
path.color = _color;
|
||||
path.originCanvasSize = self.frame.size;
|
||||
[_pathList addObject:path];
|
||||
[_redoPathList removeAllObjects];
|
||||
[_currentPathPoints removeAllObjects];
|
||||
_currentPath = nil;
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)smoothBezierPathFromPoints:(NSMutableArray<NSValue *> *) pathPoints scale:(CGFloat)scale{
|
||||
UIBezierPath *path = [UIBezierPath bezierPath];
|
||||
path.lineCapStyle = kCGLineCapRound;
|
||||
path.lineJoinStyle = kCGLineJoinRound;
|
||||
path.lineWidth = GRAFFITI_LINE_WIDTH * scale;
|
||||
|
||||
if (pathPoints.count <= 1) {
|
||||
return nil;
|
||||
}
|
||||
[path moveToPoint:CGPointMultiply(pathPoints.firstObject.CGPointValue, scale)];
|
||||
if (pathPoints.count == 2) {
|
||||
[path addLineToPoint:CGPointMultiply(pathPoints.lastObject.CGPointValue,scale)];
|
||||
return path;
|
||||
}
|
||||
|
||||
for (int i = 3; i < pathPoints.count; i++) {
|
||||
CGPoint p = CGPointMultiply(pathPoints[i].CGPointValue, scale);
|
||||
CGPoint prev = CGPointMultiply(pathPoints[i - 1].CGPointValue, scale);
|
||||
CGPoint midPoint = Vec2Mul(Vec2AddVector(p, prev), 0.5);
|
||||
[path addQuadCurveToPoint:midPoint controlPoint:prev];
|
||||
}
|
||||
[path addLineToPoint:CGPointMultiply(pathPoints.lastObject.CGPointValue, scale)];
|
||||
return path;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPath
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_color = UIColor.clearColor;
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2025 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TUIMultimediaMosaicDrawView : UIImageView
|
||||
@property (nonatomic, strong) UIImage *originalImage;
|
||||
@property(readonly, nonatomic) BOOL canUndo;
|
||||
@property(readonly, nonatomic) BOOL canRedo;
|
||||
@property(readonly, nonatomic) NSInteger pathCount;
|
||||
|
||||
- (void)clearMosaic;
|
||||
- (void)undo;
|
||||
- (void)redo;
|
||||
- (void)addPathPoint:(CGPoint) point;
|
||||
- (void)completeAddPoint;
|
||||
@end
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright (c) 2025 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaMosaicDrawView.h"
|
||||
#import <CoreImage/CoreImage.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaDrawView.h"
|
||||
|
||||
#define MOSAIC_LINE_WIDTH 20
|
||||
#define MOSAIC_SCALE_VALUE 50
|
||||
|
||||
@interface TUIMultimediaMosaicDrawView () {
|
||||
UIBezierPath *_currentPath;
|
||||
|
||||
NSMutableArray<CAShapeLayer *> *_layerList;
|
||||
NSMutableArray<CAShapeLayer *> *_redoLayerList;
|
||||
|
||||
CALayer *_maskParentLayer;
|
||||
CAShapeLayer *_currentShapeLayer;
|
||||
BOOL _isAdddPathFirstPoint;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaMosaicDrawView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self setupView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupView {
|
||||
NSLog(@"Mosaic setupView");
|
||||
_maskParentLayer = [CALayer layer];
|
||||
_maskParentLayer.frame = self.bounds;
|
||||
self.layer.mask = _maskParentLayer;
|
||||
|
||||
_layerList = [NSMutableArray array];
|
||||
_redoLayerList = [NSMutableArray array];
|
||||
_isAdddPathFirstPoint = YES;
|
||||
}
|
||||
|
||||
- (void)setOriginalImage:(UIImage *)originalImage {
|
||||
NSLog(@"Mosaic setOriginalImage");
|
||||
_originalImage = originalImage;
|
||||
self.image = [self generateMosaicImage:originalImage];
|
||||
}
|
||||
|
||||
-(void)addPathPoint:(CGPoint) point {
|
||||
if (_isAdddPathFirstPoint) {
|
||||
_isAdddPathFirstPoint = NO;
|
||||
_currentShapeLayer = [self createShapeLayer];
|
||||
[_maskParentLayer addSublayer:_currentShapeLayer];
|
||||
[_layerList addObject:_currentShapeLayer];
|
||||
_currentPath = [UIBezierPath bezierPath];
|
||||
[_currentPath moveToPoint:point];
|
||||
} else {
|
||||
[_currentPath addLineToPoint:point];
|
||||
_currentShapeLayer.path = _currentPath.CGPath;
|
||||
}
|
||||
}
|
||||
|
||||
-(CAShapeLayer*)createShapeLayer{
|
||||
CAShapeLayer* shapeLayer = [CAShapeLayer layer];
|
||||
shapeLayer.frame = self.bounds;
|
||||
shapeLayer.lineCap = kCALineCapRound;
|
||||
shapeLayer.lineJoin = kCALineJoinRound;
|
||||
shapeLayer.lineWidth = MOSAIC_LINE_WIDTH;
|
||||
shapeLayer.strokeColor = [UIColor blackColor].CGColor;
|
||||
shapeLayer.fillColor = nil;
|
||||
return shapeLayer;
|
||||
}
|
||||
|
||||
-(void)completeAddPoint {
|
||||
_isAdddPathFirstPoint = YES;
|
||||
for (CAShapeLayer * layer in _redoLayerList) {
|
||||
[self safeRemoveShapeLayer:layer];
|
||||
}
|
||||
[_redoLayerList removeAllObjects];
|
||||
}
|
||||
|
||||
- (NSInteger) pathCount {
|
||||
return _layerList.count;
|
||||
}
|
||||
|
||||
- (BOOL)canUndo {
|
||||
return _layerList.count != 0;
|
||||
}
|
||||
|
||||
- (BOOL)canRedo {
|
||||
return _redoLayerList.count != 0;
|
||||
}
|
||||
|
||||
- (void)clearMosaic {
|
||||
for (CAShapeLayer * layer in _layerList) {
|
||||
[self safeRemoveShapeLayer:layer];
|
||||
}
|
||||
|
||||
for (CAShapeLayer * layer in _redoLayerList) {
|
||||
[self safeRemoveShapeLayer:layer];
|
||||
}
|
||||
|
||||
[_currentPath removeAllPoints];
|
||||
[_layerList removeAllObjects];
|
||||
[_redoLayerList removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)undo {
|
||||
if (_layerList.count == 0) {
|
||||
return;
|
||||
}
|
||||
CAShapeLayer *layer = [_layerList lastObject];
|
||||
[_layerList removeLastObject];
|
||||
layer.hidden = YES;
|
||||
[_redoLayerList addObject:layer];
|
||||
}
|
||||
|
||||
- (void)redo {
|
||||
if (_redoLayerList.count == 0) {
|
||||
return;
|
||||
}
|
||||
CAShapeLayer *layer = [_redoLayerList lastObject];
|
||||
[_redoLayerList removeLastObject];
|
||||
layer.hidden = NO;
|
||||
[_layerList addObject:layer];
|
||||
}
|
||||
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_maskParentLayer.frame = self.bounds;
|
||||
|
||||
for (CAShapeLayer *layer in _maskParentLayer.sublayers) {
|
||||
if ([layer isKindOfClass:[CAShapeLayer class]]) {
|
||||
float scale = self.bounds.size.width / layer.frame.size.width;
|
||||
layer.frame = self.bounds;
|
||||
CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
|
||||
CGPathRef scaledPath = CGPathCreateCopyByTransformingPath(layer.path, &transform);
|
||||
layer.lineWidth = layer.lineWidth * scale;
|
||||
layer.path = scaledPath;
|
||||
CGPathRelease(scaledPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (UIImage *)generateMosaicImage:(UIImage *)inputImage {
|
||||
CIImage *ciImage = [[CIImage alloc] initWithImage:inputImage];
|
||||
|
||||
CIFilter *filter = [CIFilter filterWithName:@"CIPixellate"];
|
||||
[filter setValue:ciImage forKey:kCIInputImageKey];
|
||||
[filter setValue:@(MOSAIC_SCALE_VALUE) forKey:kCIInputScaleKey];
|
||||
|
||||
CIImage *outputImage = filter.outputImage;
|
||||
CIContext *context = [CIContext contextWithOptions:nil];
|
||||
CGImageRef cgImage = [context createCGImage:outputImage fromRect:outputImage.extent];
|
||||
return [UIImage imageWithCGImage:cgImage];
|
||||
}
|
||||
|
||||
- (void)safeRemoveShapeLayer:(CAShapeLayer *)layer {
|
||||
if (!layer) return;
|
||||
|
||||
[layer removeAllAnimations];
|
||||
layer.delegate = nil;
|
||||
layer.path = NULL;
|
||||
layer.fillColor = NULL;
|
||||
layer.strokeColor = NULL;
|
||||
[layer removeFromSuperlayer];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaPasterSelectViewDelegate;
|
||||
|
||||
@interface TUIMultimediaPasterSelectView : UIView
|
||||
@property(nonatomic) TUIMultimediaPasterConfig *config;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaPasterSelectViewDelegate> delegate;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaPasterSelectViewDelegate <NSObject>
|
||||
- (void)onPasterSelected:(UIImage *)image;
|
||||
- (void)pasterSelectView:(TUIMultimediaPasterSelectView *)v needAddCustomPaster:(TUIMultimediaPasterGroupConfig *)group completeCallback:(void (^)(void))callback;
|
||||
- (void)pasterSelectView:(TUIMultimediaPasterSelectView *)v
|
||||
needDeleteCustomPasterInGroup:(TUIMultimediaPasterGroupConfig *)group
|
||||
index:(NSInteger)index
|
||||
completeCallback:(void (^)(BOOL deleted))callback;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPasterSelectView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImageCell.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaTabPanel.h"
|
||||
|
||||
static const CGFloat ItemInsect = 10;
|
||||
static const CGFloat ItemCountPerLine = 5;
|
||||
static const CGFloat ItemCountPerColumn = 3;
|
||||
|
||||
@interface TUIMultimediaPasterSelectView () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, TUIMultimediaTabPanelDelegate> {
|
||||
TUIMultimediaTabPanel *_tabPanel;
|
||||
UILongPressGestureRecognizer *_longPressRec;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPasterSelectView
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_config = [[TUIMultimediaPasterConfig alloc] init];
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reloadTabPanel {
|
||||
for (TUIMultimediaTabPanelTab *tab in _tabPanel.tabs) {
|
||||
if (![tab.view isKindOfClass:UICollectionView.class]) continue;
|
||||
UICollectionView *v = (UICollectionView *)tab.view;
|
||||
[v reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
for (TUIMultimediaTabPanelTab *tab in _tabPanel.tabs) {
|
||||
if (![tab.view isKindOfClass:UICollectionView.class]) continue;
|
||||
UICollectionView *v = (UICollectionView *)tab.view;
|
||||
CGFloat len = (self.bounds.size.width - ItemInsect * (ItemCountPerLine - 1)) / ItemCountPerLine;
|
||||
[v mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.equalTo(self);
|
||||
make.height.mas_equalTo(len * ItemCountPerColumn + ItemInsect * (ItemCountPerColumn - 1));
|
||||
}];
|
||||
[v reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UI init
|
||||
|
||||
- (void)initUI {
|
||||
_tabPanel = [[TUIMultimediaTabPanel alloc] init];
|
||||
_tabPanel.backgroundColor = TUIMultimediaPluginDynamicColor(@"editor_popup_view_bg_color", @"#000000BF");
|
||||
_tabPanel.delegate = self;
|
||||
[self addSubview:_tabPanel];
|
||||
|
||||
[_tabPanel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self);
|
||||
}];
|
||||
|
||||
_longPressRec = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onCollectionViewLongPress:)];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource protocol
|
||||
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIMultimediaImageCell *cell = (TUIMultimediaImageCell *)[collectionView dequeueReusableCellWithReuseIdentifier:TUIMultimediaImageCell.reuseIdentifier forIndexPath:indexPath];
|
||||
TUIMultimediaPasterGroupConfig *config = _config.groups[collectionView.tag];
|
||||
cell.image = [config.itemList[indexPath.item] loadIcon];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
TUIMultimediaPasterGroupConfig *config = _config.groups[collectionView.tag];
|
||||
return config.itemList.count;
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDelegate protocol
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIMultimediaPasterGroupConfig *config = _config.groups[collectionView.tag];
|
||||
TUIMultimediaPasterItemConfig *item = config.itemList[indexPath.item];
|
||||
if (item.isAddButton) {
|
||||
[_delegate pasterSelectView:self
|
||||
needAddCustomPaster:config
|
||||
completeCallback:^{
|
||||
[self reloadTabPanel];
|
||||
}];
|
||||
return;
|
||||
}
|
||||
[_delegate onPasterSelected:[item loadImage]];
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
CGFloat len = (self.bounds.size.width - ItemInsect * (ItemCountPerLine + 1)) / ItemCountPerLine;
|
||||
return CGSizeMake(len, len);
|
||||
}
|
||||
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
minimumLineSpacingForSectionAtIndex:(NSInteger)section {
|
||||
return ItemInsect;
|
||||
}
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
|
||||
return ItemInsect;
|
||||
}
|
||||
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
insetForSectionAtIndex:(NSInteger)section {
|
||||
return UIEdgeInsetsMake(ItemInsect, ItemInsect, 0, ItemInsect);
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)onCollectionViewLongPress:(UILongPressGestureRecognizer *)rec {
|
||||
UICollectionView *collectionView = (UICollectionView *)rec.view;
|
||||
CGPoint p = [rec locationInView:collectionView];
|
||||
NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:p];
|
||||
if (indexPath == nil) {
|
||||
return;
|
||||
}
|
||||
TUIMultimediaPasterGroupConfig *config = _config.groups[_tabPanel.selectedIndex];
|
||||
TUIMultimediaPasterItemConfig *item = config.itemList[indexPath.item];
|
||||
if (!item.isUserAdded) {
|
||||
return;
|
||||
}
|
||||
[_delegate pasterSelectView:self
|
||||
needDeleteCustomPasterInGroup:_config.groups[_tabPanel.selectedIndex]
|
||||
index:indexPath.item
|
||||
completeCallback:^(BOOL deleted) {
|
||||
if (deleted) {
|
||||
[collectionView reloadData];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaTabPanelDelegate
|
||||
- (void)tabPanel:(TUIMultimediaTabPanel *)panel selectedIndexChanged:(NSInteger)selectedIndex {
|
||||
[panel.tabs[selectedIndex].view addGestureRecognizer:_longPressRec];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (void)setConfig:(TUIMultimediaPasterConfig *)config {
|
||||
_config = config;
|
||||
_tabPanel.tabs = [_config.groups tui_multimedia_mapWithIndex:^id(TUIMultimediaPasterGroupConfig *config, NSUInteger idx) {
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
UICollectionView *cv = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
cv.backgroundColor = UIColor.clearColor;
|
||||
cv.showsVerticalScrollIndicator = NO;
|
||||
cv.delegate = self;
|
||||
cv.dataSource = self;
|
||||
cv.tag = idx;
|
||||
[cv registerClass:TUIMultimediaImageCell.class forCellWithReuseIdentifier:TUIMultimediaImageCell.reuseIdentifier];
|
||||
NSString *localizedName = [TUIMultimediaCommon localizedStringForKey:config.name];
|
||||
UIImage *icon = [config loadIcon];
|
||||
return [[TUIMultimediaTabPanelTab alloc] initWithName:localizedName icon:icon view:cv];
|
||||
}];
|
||||
[_tabPanel.tabs.firstObject.view addGestureRecognizer:_longPressRec];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleInfo.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaSubtitleEditViewDelegate;
|
||||
|
||||
@interface TUIMultimediaSubtitleEditView : UIView
|
||||
@property(nonatomic) TUIMultimediaSubtitleInfo *subtitleInfo;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaSubtitleEditViewDelegate> delegate;
|
||||
- (void)activate;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaSubtitleEditViewDelegate <NSObject>
|
||||
- (void)subtitleEditViewOnOk:(TUIMultimediaSubtitleEditView *)view;
|
||||
- (void)subtitleEditViewOnCancel:(TUIMultimediaSubtitleEditView *)view;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaSubtitleEditView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaColorPanel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaGeometry.h"
|
||||
|
||||
@interface TUIMultimediaSubtitleEditView () <TUIMultimediaColorPanelDelegate, UITextViewDelegate> {
|
||||
UIButton *_btnOk;
|
||||
UIButton *_btnCancel;
|
||||
UITextView *_textView;
|
||||
TUIMultimediaColorPanel *_colorPanel;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaSubtitleEditView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_subtitleInfo = [[TUIMultimediaSubtitleInfo alloc] init];
|
||||
[self initUI];
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)activate {
|
||||
[_textView becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
self.backgroundColor = TUIMultimediaPluginDynamicColor(@"editor_popup_view_bg_color", @"#000000BF");
|
||||
|
||||
_btnOk = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[_btnOk setTitle:[TUIMultimediaCommon localizedStringForKey:@"ok"] forState:UIControlStateNormal];
|
||||
[_btnOk setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
_btnOk.titleLabel.font = [UIFont systemFontOfSize:20];
|
||||
[_btnOk addTarget:self action:@selector(onOk) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:_btnOk];
|
||||
|
||||
_btnCancel = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[_btnCancel setTitle:[TUIMultimediaCommon localizedStringForKey:@"cancel"] forState:UIControlStateNormal];
|
||||
[_btnCancel setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
_btnCancel.titleLabel.font = [UIFont systemFontOfSize:20];
|
||||
_btnCancel.titleLabel.textColor = UIColor.whiteColor;
|
||||
[_btnCancel addTarget:self action:@selector(onCancel) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self addSubview:_btnCancel];
|
||||
|
||||
_textView = [[UITextView alloc] init];
|
||||
_textView.backgroundColor = UIColor.clearColor;
|
||||
_textView.font = [UIFont systemFontOfSize:20];
|
||||
_textView.text = _subtitleInfo.text;
|
||||
_textView.textColor = _subtitleInfo.color;
|
||||
_textView.delegate = self;
|
||||
[self addSubview:_textView];
|
||||
|
||||
_colorPanel = [[TUIMultimediaColorPanel alloc] init];
|
||||
_colorPanel.delegate = self;
|
||||
[self addSubview:_colorPanel];
|
||||
|
||||
[_btnOk mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.mas_safeAreaLayoutGuideTop);
|
||||
make.right.equalTo(self).inset(30);
|
||||
make.width.mas_greaterThanOrEqualTo(100);
|
||||
make.height.mas_greaterThanOrEqualTo(50);
|
||||
}];
|
||||
[_btnCancel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.mas_safeAreaLayoutGuideTop);
|
||||
make.left.equalTo(self).inset(30);
|
||||
make.width.mas_greaterThanOrEqualTo(100);
|
||||
make.height.mas_greaterThanOrEqualTo(50);
|
||||
}];
|
||||
[_textView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self).inset(50);
|
||||
make.top.equalTo(_btnOk.mas_bottom).inset(50);
|
||||
make.bottom.equalTo(_colorPanel.mas_top).inset(10);
|
||||
}];
|
||||
[_colorPanel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self).inset(10);
|
||||
make.height.mas_equalTo(32);
|
||||
make.bottom.equalTo(self);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)onOk {
|
||||
NSMutableString *wrappedText = [NSMutableString string];
|
||||
NSString *text = _textView.text;
|
||||
NSLayoutManager *layoutManager = _textView.layoutManager;
|
||||
NSUInteger numberOfLines, index;
|
||||
NSUInteger numberOfGlyphs = [layoutManager numberOfGlyphs];
|
||||
BOOL lastLineBreak = NO;
|
||||
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++) {
|
||||
NSRange lineRange;
|
||||
[layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&lineRange];
|
||||
// NSLog(@"TUIMultimedia Subtitle Line:%@", [text substringWithRange:lineRange]);
|
||||
index = NSMaxRange(lineRange);
|
||||
NSString *line = [text substringWithRange:lineRange];
|
||||
if (numberOfLines != 0 && !lastLineBreak) {
|
||||
[wrappedText appendString:@"\n"];
|
||||
}
|
||||
[wrappedText appendString:line];
|
||||
lastLineBreak = [line containsString:@"\n"];
|
||||
}
|
||||
_subtitleInfo.text = _textView.text;
|
||||
_subtitleInfo.wrappedText = wrappedText;
|
||||
[_delegate subtitleEditViewOnOk:self];
|
||||
}
|
||||
|
||||
- (void)onCancel {
|
||||
[_delegate subtitleEditViewOnCancel:self];
|
||||
}
|
||||
|
||||
- (void)keyboardWillShow:(NSNotification *)notification {
|
||||
CGRect rect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
[_colorPanel mas_updateConstraints:^(MASConstraintMaker *make) {
|
||||
make.bottom.equalTo(self).offset(-CGRectGetHeight(rect));
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)keyboardWillHide:(NSNotification *)notification {
|
||||
[self onCancel];
|
||||
}
|
||||
|
||||
#pragma mark - UITextViewDelegate protocol
|
||||
- (void)onColorPanel:(TUIMultimediaColorPanel *)panel selectColor:(UIColor *)color {
|
||||
_subtitleInfo.color = color;
|
||||
_textView.textColor = color;
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
- (void)setSubtitleInfo:(TUIMultimediaSubtitleInfo *)subtitleInfo {
|
||||
_subtitleInfo = subtitleInfo;
|
||||
_textView.textColor = _subtitleInfo.color;
|
||||
_textView.text = subtitleInfo.text;
|
||||
_colorPanel.selectedColor = _subtitleInfo.color;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^TUIMultimediaPictureEditorControllerCallback)(UIImage * _Nullable outImage, int resultCode);
|
||||
|
||||
@interface TUIMultimediaPictureEditorController : UIViewController
|
||||
@property(nonatomic) UIImage *srcPicture;
|
||||
@property(nonatomic) TUIMultimediaPictureEditorControllerCallback completeCallback;
|
||||
@property(nonatomic)int sourceType;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,286 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaPictureEditorController.h"
|
||||
#import <ReactiveObjC/ReactiveObjC.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TXLiteAVSDK_Professional/TXPictureEditer.h>
|
||||
#import <TXLiteAVSDK_Professional/TXVideoEditerTypeDef.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommonEditorControlView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConstant.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterSelectController.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleEditController.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSticker.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaAuthorizationPrompter.h"
|
||||
|
||||
@interface TUIMultimediaPictureEditorController () <TUIMultimediaCommonEditorControlViewDelegate, TUIMultimediaPasterSelectControllerDelegate> {
|
||||
TXPictureEditer *_editor;
|
||||
UIImageView *_imgView;
|
||||
|
||||
TUIMultimediaPasterSelectController *_pasterSelectController;
|
||||
TUIMultimediaSubtitleEditController *_subtitleEditController;
|
||||
TUIMultimediaCommonEditorControlView *_commonEditCtrlView;
|
||||
BOOL _originNavgationBarHidden;
|
||||
BOOL _hasCrop;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaPictureEditorController
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_hasCrop = false;
|
||||
_sourceType = SOURCE_TYPE_RECORD;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
_editor = [[TXPictureEditer alloc] init];
|
||||
|
||||
_pasterSelectController = [[TUIMultimediaPasterSelectController alloc] init];
|
||||
_pasterSelectController.delegate = self;
|
||||
_pasterSelectController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
|
||||
_subtitleEditController = [[TUIMultimediaSubtitleEditController alloc] init];
|
||||
_subtitleEditController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
|
||||
_commonEditCtrlView = [[TUIMultimediaCommonEditorControlView alloc] initWithConfig:TUIMultimediaCommonEditorConfig.configForPictureEditor];
|
||||
[self.view addSubview:_commonEditCtrlView];
|
||||
_commonEditCtrlView.delegate = self;
|
||||
_commonEditCtrlView.clipsToBounds = true;
|
||||
_commonEditCtrlView.sourceType = _sourceType;
|
||||
_commonEditCtrlView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
|
||||
|
||||
_commonEditCtrlView.previewSize = _srcPicture.size;
|
||||
_imgView = [[UIImageView alloc] init];
|
||||
_imgView.image = _srcPicture;
|
||||
[_commonEditCtrlView.previewView addSubview:_imgView];
|
||||
[_imgView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(_commonEditCtrlView.previewView);
|
||||
}];
|
||||
_commonEditCtrlView.mosaciOriginalImage = _srcPicture;
|
||||
|
||||
UIPinchGestureRecognizer *pinchRec = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(onPinch:)];
|
||||
[self.view addGestureRecognizer:pinchRec];
|
||||
|
||||
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPanGesture:)];
|
||||
[self.view addGestureRecognizer:panGesture];
|
||||
}
|
||||
|
||||
- (void)onPinch:(UIPinchGestureRecognizer *)gestureRecognizer {
|
||||
switch (gestureRecognizer.state) {
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
if (gestureRecognizer.numberOfTouches < 2) {
|
||||
break;
|
||||
}
|
||||
CGPoint touchPoint1 = [gestureRecognizer locationOfTouch:0 inView:self.view];
|
||||
CGPoint touchPoint2 = [gestureRecognizer locationOfTouch:1 inView:self.view];
|
||||
CGPoint scaleCenter = CGPointMake((touchPoint1.x + touchPoint2.x) / 2, (touchPoint1.y + touchPoint2.y) / 2);
|
||||
[_commonEditCtrlView previewScale:gestureRecognizer.scale center:scaleCenter];
|
||||
gestureRecognizer.scale = 1.0f;
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateEnded:
|
||||
[_commonEditCtrlView previewAdjustToLimitRect];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onPanGesture:(UIPanGestureRecognizer *)gestureRecognizer {
|
||||
switch (gestureRecognizer.state) {
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
CGPoint offset = [gestureRecognizer translationInView:self.view];
|
||||
[gestureRecognizer setTranslation:CGPointZero inView:self.view];
|
||||
[_commonEditCtrlView previewMove:offset];
|
||||
break;
|
||||
}
|
||||
case UIGestureRecognizerStateEnded:
|
||||
[_commonEditCtrlView previewAdjustToLimitRect];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
if (self.navigationController != nil) {
|
||||
_originNavgationBarHidden = self.navigationController.navigationBarHidden;
|
||||
self.navigationController.navigationBarHidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
if (self.navigationController != nil) {
|
||||
self.navigationController.navigationBarHidden = _originNavgationBarHidden;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (void)setSrcPicture:(UIImage *)photo {
|
||||
_srcPicture = photo;
|
||||
_imgView.image = _srcPicture;
|
||||
_commonEditCtrlView.previewSize = _srcPicture.size;
|
||||
}
|
||||
|
||||
- (void)setSourceType:(int)sourceType {
|
||||
NSLog(@"setSourceType sourceType = %d",sourceType);
|
||||
_sourceType = sourceType;
|
||||
if (_commonEditCtrlView != nil) {
|
||||
_commonEditCtrlView.sourceType = sourceType;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaCommonEditorControlViewDelegate protocol
|
||||
- (void)onCommonEditorControlViewCancel:(nonnull TUIMultimediaCommonEditorControlView *)view {
|
||||
if (_completeCallback == nil) {
|
||||
return;
|
||||
}
|
||||
_completeCallback(nil, PHOTO_EDIT_RESULT_CODE_CANCEL);
|
||||
}
|
||||
|
||||
- (void)onCommonEditorControlViewCancelGenerate:(nonnull TUIMultimediaCommonEditorControlView *)view {
|
||||
}
|
||||
|
||||
- (void)onCommonEditorControlViewComplete:(nonnull TUIMultimediaCommonEditorControlView *)view
|
||||
stickers:(nonnull NSArray<TUIMultimediaSticker *> *)stickers {
|
||||
if (![TUIMultimediaAuthorizationPrompter verifyPermissionGranted:self]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_completeCallback == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stickers.count == 0) {
|
||||
NSLog(@"Return directly without edit the picture");
|
||||
UIImage* outputImage = (_hasCrop || _sourceType == SOURCE_TYPE_RECORD) ? _srcPicture : nil;
|
||||
_completeCallback(outputImage, _hasCrop ? VIDEO_EDIT_RESULT_CODE_GENERATE_SUCCESS : VIDEO_EDIT_RESULT_CODE_NO_EDIT);
|
||||
return;
|
||||
}
|
||||
|
||||
[_editor setPicture:_srcPicture];
|
||||
[_editor setOutputRotation:0];
|
||||
[_editor setOutputSize:_srcPicture.size.width height:_srcPicture.size.height];
|
||||
[_editor setPasterList:[self stickercConvertToPaster:stickers rotationAngle:0]];
|
||||
@weakify(self)
|
||||
[_editor processPicture:^(UIImage *processedPicture) {
|
||||
@strongify(self)
|
||||
self.completeCallback(processedPicture, PHOTO_EDIT_RESULT_CODE_GENERATE_SUCCESS);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)onCommonEditorControlViewCrop:(NSInteger)rotationAngle normalizedCropRect:(CGRect)normalizedCropRect
|
||||
stickers:(NSArray<TUIMultimediaSticker *> *)stickers {
|
||||
[_editor setPicture:_srcPicture];
|
||||
[_editor setPasterList:[self stickercConvertToPaster:stickers rotationAngle:rotationAngle]];
|
||||
normalizedCropRect = [self adjustCropRectAccordingRotaionAngle:normalizedCropRect rotationAngle:rotationAngle];
|
||||
CGRect cropRect = CGRectMake(normalizedCropRect.origin.x * _srcPicture.size.width * _srcPicture.scale,
|
||||
normalizedCropRect.origin.y * _srcPicture.size.height * _srcPicture.scale,
|
||||
normalizedCropRect.size.width * _srcPicture.size.width * _srcPicture.scale,
|
||||
normalizedCropRect.size.height * _srcPicture.size.height * _srcPicture.scale);
|
||||
[_editor setCropRect:cropRect];
|
||||
|
||||
|
||||
CGSize outputSize = [self getOutputSize:cropRect.size rotationAngle:rotationAngle];
|
||||
[_editor setOutputSize:outputSize.width height:outputSize.height];
|
||||
[_editor setOutputRotation:(CGFloat)rotationAngle];
|
||||
[_editor processPicture:^(UIImage *processedPicture) {
|
||||
[self onProcessPictureForCrop:processedPicture];
|
||||
}];
|
||||
}
|
||||
|
||||
- (CGSize) getOutputSize:(CGSize) cropRectSize rotationAngle:(CGFloat) rotationAngle {
|
||||
int outputWidth = 1080;
|
||||
int outputHeight = 1920;
|
||||
if(((int)rotationAngle + 180) % 180 == 0) {
|
||||
outputHeight = cropRectSize.height / cropRectSize.width * outputWidth;
|
||||
} else {
|
||||
outputHeight = cropRectSize.width / cropRectSize.height * outputWidth;
|
||||
}
|
||||
return CGSizeMake(outputWidth, outputHeight);
|
||||
}
|
||||
|
||||
- (NSArray<TXPaster *> *) stickercConvertToPaster:(nonnull NSArray<TUIMultimediaSticker *> *)stickers rotationAngle:(NSInteger)rotationAngle {
|
||||
CGSize previewSize = _commonEditCtrlView.previewView.frame.size;
|
||||
return [stickers tui_multimedia_map:^TXPaster *(TUIMultimediaSticker *s) {
|
||||
TXPaster *p = [[TXPaster alloc] init];
|
||||
p.pasterImage = s.image;
|
||||
CGRect frame = CGRectMake(s.frame.origin.x / previewSize.width,
|
||||
s.frame.origin.y / previewSize.height,
|
||||
s.frame.size.width / previewSize.width,
|
||||
s.frame.size.height / previewSize.height);
|
||||
p.frame = frame;
|
||||
return p;
|
||||
}];
|
||||
}
|
||||
|
||||
- (CGRect) adjustCropRectAccordingRotaionAngle:(CGRect)normalizedCropRect rotationAngle:(CGFloat)rotationAngle {
|
||||
normalizedCropRect = CGRectMake(MIN(1.0f, MAX(normalizedCropRect.origin.x, 0)),
|
||||
MIN(1.0f, MAX(normalizedCropRect.origin.y, 0)),
|
||||
MIN(1.0f, MAX(normalizedCropRect.size.width, 0)),
|
||||
MIN(1.0f, MAX(normalizedCropRect.size.height, 0)));
|
||||
|
||||
if (rotationAngle == 90) {
|
||||
normalizedCropRect = CGRectMake(normalizedCropRect.origin.y,
|
||||
1 - normalizedCropRect.size.width - normalizedCropRect.origin.x,
|
||||
normalizedCropRect.size.height,
|
||||
normalizedCropRect.size.width);
|
||||
} else if (rotationAngle == 180) {
|
||||
normalizedCropRect = CGRectMake(1 - normalizedCropRect.size.width - normalizedCropRect.origin.x,
|
||||
1 - normalizedCropRect.size.height - normalizedCropRect.origin.y,
|
||||
normalizedCropRect.size.width,
|
||||
normalizedCropRect.size.height);
|
||||
} else if (rotationAngle == 270) {
|
||||
normalizedCropRect = CGRectMake(1 - normalizedCropRect.origin.y - normalizedCropRect.size.height,
|
||||
normalizedCropRect.origin.x,
|
||||
normalizedCropRect.size.height,
|
||||
normalizedCropRect.size.width);
|
||||
}
|
||||
return normalizedCropRect;
|
||||
}
|
||||
|
||||
- (void) onProcessPictureForCrop:(UIImage *) processedPicture {
|
||||
_hasCrop = true;
|
||||
_srcPicture = processedPicture;
|
||||
_commonEditCtrlView.previewView.hidden = NO;
|
||||
_commonEditCtrlView.previewSize = _srcPicture.size;
|
||||
_imgView.image = _srcPicture;
|
||||
_commonEditCtrlView.mosaciOriginalImage = _srcPicture;
|
||||
}
|
||||
|
||||
- (void)onCommonEditorControlViewNeedAddPaster:(TUIMultimediaCommonEditorControlView *)view {
|
||||
_commonEditCtrlView.modifyButtonsHidden = YES;
|
||||
[self presentViewController:_pasterSelectController animated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)onCommonEditorControlViewNeedModifySubtitle:(TUIMultimediaSubtitleInfo *)info callback:(void (^)(TUIMultimediaSubtitleInfo *info, BOOL isOk))callback {
|
||||
_subtitleEditController.subtitleInfo = info;
|
||||
_subtitleEditController.callback = ^(TUIMultimediaSubtitleEditController *c, BOOL isOk) {
|
||||
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
if (callback != nil) {
|
||||
callback(c.subtitleInfo, isOk);
|
||||
}
|
||||
};
|
||||
[self presentViewController:_subtitleEditController animated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)onCommonEditorControlViewNeedEditMusic:(nonnull TUIMultimediaCommonEditorControlView *)view {
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaPasterSelectControllerDelegate protocol
|
||||
- (void)pasterSelectController:(TUIMultimediaPasterSelectController *)c onPasterSelected:(UIImage *)image {
|
||||
[_commonEditCtrlView addPaster:image];
|
||||
_commonEditCtrlView.modifyButtonsHidden = NO;
|
||||
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
- (void)onPasterSelectControllerExit:(TUIMultimediaPasterSelectController *)c {
|
||||
_commonEditCtrlView.modifyButtonsHidden = NO;
|
||||
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <AVKit/AVKit.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPopupController.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaVideoBgmEditInfo.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaBGMEditControllerDelegate;
|
||||
|
||||
/**
|
||||
背景音乐选择界面Controller
|
||||
*/
|
||||
@interface TUIMultimediaBGMEditController : TUIMultimediaPopupController
|
||||
@property(nonatomic) float clipDuration;
|
||||
@property(readonly, nonatomic) TUIMultimediaVideoBgmEditInfo *bgmEditInfo;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaBGMEditControllerDelegate> delegate;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaBGMEditControllerDelegate <NSObject>
|
||||
- (void)onBGMEditController:(TUIMultimediaBGMEditController *)c bgmInfoChanged:(TUIMultimediaVideoBgmEditInfo *)bgmInfo;
|
||||
- (void)onBGMEditControllerExit:(TUIMultimediaBGMEditController *)c;
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaBGMEditController.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <ReactiveObjC/RACEXTScope.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaBGMEditView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImagePicker.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterSelectView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPersistence.h"
|
||||
|
||||
@interface TUIMultimediaBGMEditController () <TUIMultimediaBGMEditViewDelegate> {
|
||||
TUIMultimediaBGMEditView *_editView;
|
||||
NSArray<TUIMultimediaBGMGroup *> *_bgmConfig;
|
||||
dispatch_block_t _blockBGMNotify;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaBGMEditController
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_bgmConfig = [TUIMultimediaBGMGroup loadBGMConfigs];
|
||||
_bgmEditInfo = [[TUIMultimediaVideoBgmEditInfo alloc] init];
|
||||
_bgmEditInfo.originAudio = YES;
|
||||
_bgmEditInfo.bgm = nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
_editView = [[TUIMultimediaBGMEditView alloc] init];
|
||||
_editView.bgmConfig = _bgmConfig;
|
||||
_editView.originAudioEnabled = YES;
|
||||
_editView.clipDuration = _clipDuration;
|
||||
_editView.delegate = self;
|
||||
self.mainView = _editView;
|
||||
}
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
}
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
}
|
||||
|
||||
- (void)popupControllerDidCanceled {
|
||||
[_delegate onBGMEditControllerExit:self];
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (void)setClipDuration:(float)videoDuration {
|
||||
_clipDuration = videoDuration;
|
||||
_editView.clipDuration = videoDuration;
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaBGMEditViewDelegate protocol
|
||||
- (void)bgmEditViewValueChanged:(TUIMultimediaBGMEditView *)v {
|
||||
TUIMultimediaVideoBgmEditInfo *newBgmInfo = [[TUIMultimediaVideoBgmEditInfo alloc] init];
|
||||
if (v.bgmEnabled) {
|
||||
newBgmInfo.bgm = [v.selectedBgm copy];
|
||||
} else {
|
||||
newBgmInfo.bgm = nil;
|
||||
}
|
||||
newBgmInfo.originAudio = v.originAudioEnabled;
|
||||
|
||||
// 延迟防抖, 防止拖动时产生怪音
|
||||
if (_blockBGMNotify != nil) {
|
||||
dispatch_block_cancel(_blockBGMNotify);
|
||||
_blockBGMNotify = nil;
|
||||
}
|
||||
_blockBGMNotify = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{
|
||||
self->_bgmEditInfo = newBgmInfo;
|
||||
[self->_delegate onBGMEditController:self bgmInfoChanged:self->_bgmEditInfo];
|
||||
});
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.050 * NSEC_PER_SEC)), dispatch_get_main_queue(), _blockBGMNotify);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaEncodeConfig.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
视频编辑界面Controller
|
||||
*/
|
||||
@interface TUIMultimediaVideoEditorController : UIViewController
|
||||
@property(nullable, nonatomic) NSString *sourceVideoPath;
|
||||
@property(nullable, nonatomic) NSString *resultVideoPath;
|
||||
@property(nonatomic)int sourceType;
|
||||
@property(nonatomic, nullable) void (^completeCallback)(NSString *resultVideoPath, int resultCode);
|
||||
- (instancetype)init;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaVideoEditorController.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <ReactiveObjC/RACEXTScope.h>
|
||||
#import <TUICore/TUIDefine.h>
|
||||
#import <TXLiteAVSDK_Professional/TXVideoEditer.h>
|
||||
#import <TXLiteAVSDK_Professional/TXVideoEditerTypeDef.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaBGMEditController.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommonEditorControlView.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaImagePicker.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPasterSelectController.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaPersistence.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaSubtitleEditController.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConstant.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaAuthorizationPrompter.h"
|
||||
|
||||
@interface TUIMultimediaVideoEditorController () <TXVideoPreviewListener,
|
||||
TXVideoGenerateListener,
|
||||
TUIMultimediaPasterSelectControllerDelegate,
|
||||
TUIMultimediaCommonEditorControlViewDelegate,
|
||||
TUIMultimediaBGMEditControllerDelegate> {
|
||||
TUIMultimediaPasterSelectController *_pasterSelectController;
|
||||
TUIMultimediaCommonEditorControlView *_commonEditCtrlView;
|
||||
TXVideoEditer *_editor;
|
||||
NSString *_sourceVideoPath;
|
||||
TXVideoInfo *_videoInfo;
|
||||
TUIMultimediaSubtitleEditController *_subtitleEditController;
|
||||
TUIMultimediaBGMEditController *_musicController;
|
||||
TUIMultimediaEncodeConfig *_encodeConfig;
|
||||
BOOL _originNavgationBarHidden;
|
||||
float _lastGenerateProgress;
|
||||
BOOL _hasAudioEdited;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaVideoEditorController
|
||||
|
||||
@dynamic sourceVideoPath;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_encodeConfig = [[TUIMultimediaEncodeConfig alloc] initWithVideoQuality:[[TUIMultimediaConfig sharedInstance] getVideoQuality]];
|
||||
_lastGenerateProgress = 0;
|
||||
_sourceType = SOURCE_TYPE_RECORD;
|
||||
_hasAudioEdited = NO;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[self initUI];
|
||||
_pasterSelectController = [[TUIMultimediaPasterSelectController alloc] init];
|
||||
_pasterSelectController.delegate = self;
|
||||
_pasterSelectController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
|
||||
_subtitleEditController = [[TUIMultimediaSubtitleEditController alloc] init];
|
||||
_subtitleEditController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
|
||||
_musicController = [[TUIMultimediaBGMEditController alloc] init];
|
||||
_musicController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
_musicController.delegate = self;
|
||||
_musicController.clipDuration = _videoInfo.duration;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
if (self.navigationController != nil) {
|
||||
_originNavgationBarHidden = self.navigationController.navigationBarHidden;
|
||||
self.navigationController.navigationBarHidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
if (self.navigationController != nil) {
|
||||
self.navigationController.navigationBarHidden = _originNavgationBarHidden;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)sourceVideoPath {
|
||||
return _sourceVideoPath;
|
||||
}
|
||||
|
||||
- (void)setSourceVideoPath:(NSString *)sourceVideoPath {
|
||||
_sourceVideoPath = sourceVideoPath;
|
||||
NSString *currentSourceVideoPath = sourceVideoPath;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
if (self->_sourceVideoPath != currentSourceVideoPath) return;
|
||||
self->_videoInfo = [TXVideoInfoReader getVideoInfo:self->_sourceVideoPath];
|
||||
NSLog(@"videoInfo angle = %d, width = %d, height = %d, duration = %f",
|
||||
self->_videoInfo.angle, self->_videoInfo.width, self->_videoInfo.height, self->_videoInfo.duration);
|
||||
if (self->_videoInfo.angle == 90 || self->_videoInfo.angle == 270) {
|
||||
int temp = self->_videoInfo.width;
|
||||
self->_videoInfo.width = self->_videoInfo.height;
|
||||
self->_videoInfo.height = temp;
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self tryReloadVideoAsset];
|
||||
self->_musicController.clipDuration = self->_videoInfo.duration;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setSourceType:(int)sourceType {
|
||||
NSLog(@"setSourceType sourceType = %d",sourceType);
|
||||
_sourceType = sourceType;
|
||||
if (_commonEditCtrlView != nil) {
|
||||
_commonEditCtrlView.sourceType = sourceType;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tryReloadVideoAsset {
|
||||
if (_editor == nil || _sourceVideoPath == nil) return;
|
||||
int code = [_editor setVideoPath:_sourceVideoPath];
|
||||
[_editor startPlayFromTime:0 toTime:_videoInfo.duration];
|
||||
if (code != 0) {
|
||||
NSString *title = [TUIMultimediaCommon localizedStringForKey:@"modify_load_assert_failed"];
|
||||
[self showAlertWithTitle:title message:@"" action:@"OK"];
|
||||
}
|
||||
_commonEditCtrlView.previewSize = CGSizeMake(_videoInfo.width, _videoInfo.height);
|
||||
}
|
||||
|
||||
#pragma mark - UI Init
|
||||
|
||||
- (void)initUI {
|
||||
_commonEditCtrlView = [[TUIMultimediaCommonEditorControlView alloc] initWithConfig:TUIMultimediaCommonEditorConfig.configForVideoEditor];
|
||||
[self.view addSubview:_commonEditCtrlView];
|
||||
_commonEditCtrlView.backgroundColor = UIColor.blackColor;
|
||||
_commonEditCtrlView.previewSize = CGSizeMake(_videoInfo.width, _videoInfo.height);
|
||||
_commonEditCtrlView.sourceType = _sourceType;
|
||||
_commonEditCtrlView.delegate = self;
|
||||
|
||||
TXPreviewParam *param = [[TXPreviewParam alloc] init];
|
||||
param.videoView = _commonEditCtrlView.previewView;
|
||||
param.renderMode = PREVIEW_RENDER_MODE_FILL_EDGE;
|
||||
_editor = [[TXVideoEditer alloc] initWithPreview:param];
|
||||
[self tryReloadVideoAsset];
|
||||
_editor.previewDelegate = self;
|
||||
_editor.generateDelegate = self;
|
||||
|
||||
[_commonEditCtrlView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.view);
|
||||
}];
|
||||
}
|
||||
- (void)showAlertWithTitle:(NSString *)title message:(NSString *)message action:(NSString *)action {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
|
||||
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:action style:UIAlertActionStyleDefault handler:nil];
|
||||
[alert addAction:defaultAction];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - TXVideoPreviewListener protocol
|
||||
- (void)onPreviewProgress:(CGFloat)time {
|
||||
}
|
||||
- (void)onPreviewFinished {
|
||||
[_editor startPlayFromTime:0 toTime:_videoInfo.duration];
|
||||
}
|
||||
|
||||
#pragma mark - TXVideoGenerateListener protocol
|
||||
- (void)onGenerateProgress:(float)progress {
|
||||
NSLog(@"TUIMultimediaVideoEditorController onGenerateProgress progress = %f",progress);
|
||||
if (progress - _lastGenerateProgress > 0.01f || progress == 1.0f) {
|
||||
_commonEditCtrlView.progressBarProgress = progress;
|
||||
_lastGenerateProgress = progress;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onGenerateComplete:(TXGenerateResult *)result {
|
||||
NSLog(@"TUIMultimediaVideoEditorController onGenerateComplete retCode = %ld",(long)result.retCode);
|
||||
int resultCode = (result.retCode != 0) ? VIDEO_EDIT_RESULT_CODE_GENERATE_FAIL : VIDEO_EDIT_RESULT_CODE_GENERATE_SUCCESS;
|
||||
_completeCallback(_resultVideoPath, resultCode);
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaCommonEditorControlViewDelegate protocol
|
||||
- (void)onCommonEditorControlViewComplete:(TUIMultimediaCommonEditorControlView *)view stickers:(NSArray<TUIMultimediaSticker *> *)stickers {
|
||||
if (![TUIMultimediaAuthorizationPrompter verifyPermissionGranted:self]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stickers.count == 0 && !_hasAudioEdited && _sourceType == SOURCE_TYPE_ALBUM) {
|
||||
NSLog(@"Return directly without encoding the video");
|
||||
_resultVideoPath = _sourceVideoPath;
|
||||
_completeCallback(_resultVideoPath, VIDEO_EDIT_RESULT_CODE_NO_EDIT);
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray<TXPaster *> *pasterList = [stickers tui_multimedia_map:^TXPaster *(TUIMultimediaSticker *s) {
|
||||
TXPaster *p = [[TXPaster alloc] init];
|
||||
p.pasterImage = s.image;
|
||||
p.frame = s.frame;
|
||||
p.startTime = 0.0;
|
||||
p.endTime = self->_videoInfo.duration;
|
||||
return p;
|
||||
}];
|
||||
[_editor setPasterList:pasterList];
|
||||
|
||||
_commonEditCtrlView.isGenerating = YES;
|
||||
[self.view bringSubviewToFront:_commonEditCtrlView];
|
||||
if (_resultVideoPath == nil || _resultVideoPath.length == 0) {
|
||||
_resultVideoPath = [self getOutFilePath];
|
||||
}
|
||||
[_editor setVideoBitrate:_encodeConfig.bitrate];
|
||||
[_editor generateVideo:[_encodeConfig getVideoEditCompressed] videoOutputPath:_resultVideoPath];
|
||||
}
|
||||
|
||||
- (void)onCommonEditorControlViewCancel:(TUIMultimediaCommonEditorControlView *)view {
|
||||
[_editor stopPlay];
|
||||
_completeCallback(nil, VIDEO_EDIT_RESULT_CODE_CANCEL);
|
||||
}
|
||||
|
||||
- (void)onCommonEditorControlViewNeedAddPaster:(TUIMultimediaCommonEditorControlView *)view {
|
||||
NSLog(@"onCommonEditorControlViewNeedAddPaster");
|
||||
_commonEditCtrlView.modifyButtonsHidden = YES;
|
||||
[self presentViewController:_pasterSelectController animated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)onCommonEditorControlViewNeedModifySubtitle:(TUIMultimediaSubtitleInfo *)info callback:(void (^)(TUIMultimediaSubtitleInfo *info, BOOL isOk))callback {
|
||||
_subtitleEditController.subtitleInfo = info;
|
||||
_subtitleEditController.callback = ^(TUIMultimediaSubtitleEditController *c, BOOL isOk) {
|
||||
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
if (callback != nil) {
|
||||
callback(c.subtitleInfo, isOk);
|
||||
}
|
||||
};
|
||||
[self presentViewController:_subtitleEditController animated:NO completion:nil];
|
||||
}
|
||||
- (void)onCommonEditorControlViewNeedEditMusic:(TUIMultimediaCommonEditorControlView *)view {
|
||||
_commonEditCtrlView.modifyButtonsHidden = YES;
|
||||
[self presentViewController:_musicController animated:NO completion:nil];
|
||||
}
|
||||
- (void)onCommonEditorControlViewCancelGenerate:(TUIMultimediaCommonEditorControlView *)view {
|
||||
[_editor cancelGenerate];
|
||||
TXPreviewParam *param = [[TXPreviewParam alloc] init];
|
||||
param.videoView = _commonEditCtrlView.previewView;
|
||||
param.renderMode = PREVIEW_RENDER_MODE_FILL_EDGE;
|
||||
_editor = [[TXVideoEditer alloc] initWithPreview:param];
|
||||
[self tryReloadVideoAsset];
|
||||
_editor.previewDelegate = self;
|
||||
_editor.generateDelegate = self;
|
||||
}
|
||||
|
||||
-(NSString*) getOutFilePath{
|
||||
NSDate* currentDate = [NSDate date];
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
|
||||
NSString* currentDateString = [dateFormatter stringFromDate:currentDate];
|
||||
NSString* outFileName = [NSString stringWithFormat:@"%@-%u-temp.mp4", currentDateString, arc4random()];
|
||||
return [NSTemporaryDirectory() stringByAppendingPathComponent:outFileName];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaPasterSelectControllerDelegate protocol
|
||||
- (void)pasterSelectController:(TUIMultimediaPasterSelectController *)c onPasterSelected:(UIImage *)image {
|
||||
[_commonEditCtrlView addPaster:image];
|
||||
_commonEditCtrlView.modifyButtonsHidden = NO;
|
||||
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
- (void)onPasterSelectControllerExit:(TUIMultimediaPasterSelectController *)c {
|
||||
_commonEditCtrlView.modifyButtonsHidden = NO;
|
||||
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - TUIMultimediaMusicControllerDelegate protocol
|
||||
- (void)onBGMEditController:(TUIMultimediaBGMEditController *)bgmController bgmInfoChanged:(TUIMultimediaVideoBgmEditInfo *)bgmInfo {
|
||||
if (![TUIMultimediaAuthorizationPrompter verifyPermissionGranted:bgmController]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[_editor setBGMLoop:YES];
|
||||
@weakify(self);
|
||||
[_editor setBGMAsset:bgmInfo.bgm.asset
|
||||
result:^(int result) {
|
||||
@strongify(self);
|
||||
if (result != 0) {
|
||||
NSString *title = [TUIMultimediaCommon localizedStringForKey:@"modify_load_assert_failed"];
|
||||
[self showAlertWithTitle:title message:@"" action:@"OK"];
|
||||
}
|
||||
}];
|
||||
[_editor setBGMAtVideoTime:0];
|
||||
[_editor setBGMStartTime:bgmInfo.bgm.startTime endTime:bgmInfo.bgm.endTime];
|
||||
[_editor setBGMVolume:1];
|
||||
if (bgmInfo.originAudio) {
|
||||
[_editor setVideoVolume:1];
|
||||
} else {
|
||||
[_editor setVideoVolume:0];
|
||||
}
|
||||
_commonEditCtrlView.musicEdited = bgmInfo.bgm != nil;
|
||||
|
||||
_hasAudioEdited = (!bgmInfo.originAudio) || (bgmInfo.bgm != nil);
|
||||
}
|
||||
- (void)onBGMEditControllerExit:(nonnull TUIMultimediaBGMEditController *)c {
|
||||
_commonEditCtrlView.modifyButtonsHidden = NO;
|
||||
[c.presentingViewController dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaBGM.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, TUIMultimediaMusicCellState) {
|
||||
TUIMultimediaMusicCellStateNormal,
|
||||
TUIMultimediaMusicCellStateSelected,
|
||||
TUIMultimediaMusicCellStateEnabled,
|
||||
};
|
||||
|
||||
@protocol TUIMultimediaMusicCellDelegate;
|
||||
|
||||
@interface TUIMultimediaMusicCell : UITableViewCell
|
||||
@property(nonatomic) TUIMultimediaMusicCellState state;
|
||||
@property(nullable, nonatomic) TUIMultimediaBGM *music;
|
||||
@property(nonatomic) float selectDuration;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaMusicCellDelegate> delegate;
|
||||
|
||||
+ (NSString *)reuseIdentifier;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaMusicCellDelegate <NSObject>
|
||||
- (void)musicCell:(TUIMultimediaMusicCell *)cell onEditStateChanged:(BOOL)editState;
|
||||
- (void)musicCell:(TUIMultimediaMusicCell *)cell onBGMRangeChanged:(TUIMultimediaBGM *)bgm;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaMusicCell.h"
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "Masonry/Masonry.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaAutoScrollLabel.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaConfig.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaFakeAudioWaveView.h"
|
||||
|
||||
static const CGFloat TitleFontSize = 14;
|
||||
static const CGFloat SubtitleFontSize = 12;
|
||||
|
||||
@interface TUIMultimediaMusicCell () {
|
||||
TUIMultimediaAutoScrollLabel *_lbTitle;
|
||||
UILabel *_lbSubTitle;
|
||||
UILabel *_lbDuration;
|
||||
TUIMultimediaFakeAudioWaveView *_waveView;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaMusicCell
|
||||
|
||||
+ (NSString *)reuseIdentifier {
|
||||
return NSStringFromClass([self class]);
|
||||
}
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
if (self != nil) {
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
self.contentView.backgroundColor = UIColor.clearColor;
|
||||
self.backgroundColor = UIColor.clearColor;
|
||||
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
|
||||
_lbTitle = [[TUIMultimediaAutoScrollLabel alloc] init];
|
||||
[self.contentView addSubview:_lbTitle];
|
||||
|
||||
_lbSubTitle = [[UILabel alloc] init];
|
||||
[self.contentView addSubview:_lbSubTitle];
|
||||
_lbSubTitle.font = [UIFont systemFontOfSize:SubtitleFontSize];
|
||||
_lbSubTitle.textColor = TUIMultimediaPluginDynamicColor(@"editor_bgm_text_color", @"#FFFFFF99");
|
||||
|
||||
_lbDuration = [[UILabel alloc] init];
|
||||
[self.contentView addSubview:_lbDuration];
|
||||
_lbDuration.font = [UIFont monospacedSystemFontOfSize:SubtitleFontSize weight:UIFontWeightMedium];
|
||||
_lbDuration.textColor = TUIMultimediaPluginDynamicColor(@"editor_bgm_text_color", @"#FFFFFF99");
|
||||
|
||||
_waveView = [[TUIMultimediaFakeAudioWaveView alloc] init];
|
||||
[self addSubview:_waveView];
|
||||
_waveView.hidden = YES;
|
||||
|
||||
[_lbTitle mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.top.equalTo(self.contentView).inset(10);
|
||||
make.width.mas_equalTo(120);
|
||||
}];
|
||||
[_lbSubTitle mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(_lbTitle.mas_bottom).inset(5);
|
||||
make.left.equalTo(self.contentView).inset(14);
|
||||
make.bottom.equalTo(self.contentView).inset(10);
|
||||
}];
|
||||
[_lbDuration mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.contentView);
|
||||
make.right.equalTo(self.contentView).inset(10);
|
||||
}];
|
||||
[_waveView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(_lbDuration.mas_left).inset(50);
|
||||
make.centerY.equalTo(self);
|
||||
make.height.mas_equalTo(12);
|
||||
make.width.mas_equalTo(48);
|
||||
}];
|
||||
}
|
||||
- (void)updateTitle {
|
||||
UIColor *color = UIColor.whiteColor;
|
||||
BOOL active = NO;
|
||||
if (_state == TUIMultimediaMusicCellStateEnabled) {
|
||||
color = [[TUIMultimediaConfig sharedInstance] getThemeColor];
|
||||
if (_music.lyric != nil && _music.lyric.length > 0) {
|
||||
active = YES;
|
||||
}
|
||||
}
|
||||
NSString *title = _music.lyric != nil && _music.lyric.length > 0 ? _music.lyric : _music.name;
|
||||
_lbTitle.text = [[NSAttributedString alloc] initWithString:title
|
||||
attributes:@{
|
||||
NSFontAttributeName : [UIFont systemFontOfSize:TitleFontSize],
|
||||
NSForegroundColorAttributeName : color,
|
||||
}];
|
||||
_lbTitle.active = active;
|
||||
}
|
||||
- (void)updateWave {
|
||||
switch (_state) {
|
||||
case TUIMultimediaMusicCellStateNormal:
|
||||
_waveView.hidden = YES;
|
||||
_waveView.enabled = NO;
|
||||
break;
|
||||
case TUIMultimediaMusicCellStateSelected:
|
||||
_waveView.color = TUIMultimediaPluginDynamicColor(@"editor_bgm_text_color", @"#FFFFFF99");
|
||||
_waveView.hidden = NO;
|
||||
_waveView.enabled = NO;
|
||||
break;
|
||||
case TUIMultimediaMusicCellStateEnabled:
|
||||
_waveView.color = [[TUIMultimediaConfig sharedInstance] getThemeColor];
|
||||
_waveView.hidden = NO;
|
||||
_waveView.enabled = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#pragma mark - Properties
|
||||
- (void)setState:(TUIMultimediaMusicCellState)state {
|
||||
_state = state;
|
||||
[self updateTitle];
|
||||
[self updateWave];
|
||||
}
|
||||
- (void)setMusic:(TUIMultimediaBGM *)music {
|
||||
_music = music;
|
||||
[self updateTitle];
|
||||
_lbSubTitle.text = music.source;
|
||||
float duration = music.asset == nil ? 0 : music.asset.duration.value / music.asset.duration.timescale;
|
||||
int min = (int)(duration / 60);
|
||||
_lbDuration.text = [NSString stringWithFormat:@"%02d:%02d", min, (int)(duration - min * 60)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaBGM.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol TUIMultimediaBGMEditViewDelegate;
|
||||
|
||||
@interface TUIMultimediaBGMEditView : UIView
|
||||
@property(nonatomic) NSArray<TUIMultimediaBGMGroup *> *bgmConfig;
|
||||
@property(nonatomic) float clipDuration;
|
||||
@property(nonatomic) TUIMultimediaBGM *selectedBgm;
|
||||
@property(nonatomic) BOOL originAudioEnabled;
|
||||
@property(nonatomic) BOOL bgmEnabled;
|
||||
@property(weak, nullable, nonatomic) id<TUIMultimediaBGMEditViewDelegate> delegate;
|
||||
@end
|
||||
|
||||
@protocol TUIMultimediaBGMEditViewDelegate <NSObject>
|
||||
- (void)bgmEditViewValueChanged:(TUIMultimediaBGMEditView *)v;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
// Author: eddardliu
|
||||
|
||||
#import "TUIMultimediaBGMEditView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIMultimediaPlugin/NSArray+Functional.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCheckBox.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaCommon.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaMusicCell.h"
|
||||
#import "TUIMultimediaPlugin/TUIMultimediaTabPanel.h"
|
||||
|
||||
static const CGFloat ItemInset = 10;
|
||||
static const CGFloat ItemWidthFactor = 0.7;
|
||||
|
||||
static const CGFloat CollectionViewHeight = 100;
|
||||
|
||||
@interface TUIMultimediaBGMEditView () <UITableViewDataSource, UITableViewDelegate> {
|
||||
NSArray<UITableView *> *_tableViews;
|
||||
TUIMultimediaTabPanel *_tabPanel;
|
||||
NSInteger _selectedIndex;
|
||||
TUIMultimediaCheckBox *_switchOriginAudio;
|
||||
TUIMultimediaCheckBox *_switchBgm;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIMultimediaBGMEditView
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_bgmConfig = @[];
|
||||
_selectedIndex = -1;
|
||||
[self initUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initUI {
|
||||
self.backgroundColor = TUIMultimediaPluginDynamicColor(@"editor_popup_view_bg_color", @"#000000BF");
|
||||
|
||||
_tabPanel = [[TUIMultimediaTabPanel alloc] initWithFrame:self.bounds];
|
||||
[self addSubview:_tabPanel];
|
||||
|
||||
_switchOriginAudio = [[TUIMultimediaCheckBox alloc] init];
|
||||
[self addSubview:_switchOriginAudio];
|
||||
_switchOriginAudio.text = [TUIMultimediaCommon localizedStringForKey:@"editor_origin_audio"];
|
||||
[_switchOriginAudio addTarget:self action:@selector(onSwitchOriginAudioChanged) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
_switchBgm = [[TUIMultimediaCheckBox alloc] init];
|
||||
[self addSubview:_switchBgm];
|
||||
_switchBgm.text = [TUIMultimediaCommon localizedStringForKey:@"editor_bgm"];
|
||||
[_switchBgm addTarget:self action:@selector(onSwitchBGMChanged) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
[_tabPanel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.top.equalTo(self);
|
||||
make.bottom.equalTo(_switchBgm.mas_top).inset(10);
|
||||
make.height.mas_equalTo(400);
|
||||
}];
|
||||
[_switchBgm mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.bottom.equalTo(self.mas_safeAreaLayoutGuideBottom);
|
||||
make.height.mas_equalTo(24);
|
||||
make.left.equalTo(self).inset(20);
|
||||
}];
|
||||
[_switchOriginAudio mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.height.mas_equalTo(24);
|
||||
make.bottom.equalTo(self.mas_safeAreaLayoutGuideBottom);
|
||||
make.right.equalTo(self).inset(20);
|
||||
}];
|
||||
|
||||
[self reloadConfig];
|
||||
}
|
||||
|
||||
- (void)reloadConfig {
|
||||
_tableViews = [_bgmConfig tui_multimedia_mapWithIndex:^UITableView *(TUIMultimediaBGMGroup *group, NSUInteger idx) {
|
||||
UITableView *v = [[UITableView alloc] init];
|
||||
v.tag = idx;
|
||||
v.backgroundColor = UIColor.clearColor;
|
||||
v.dataSource = self;
|
||||
v.delegate = self;
|
||||
[v registerClass:TUIMultimediaMusicCell.class forCellReuseIdentifier:TUIMultimediaMusicCell.reuseIdentifier];
|
||||
return v;
|
||||
}];
|
||||
_tabPanel.tabs = [_tableViews tui_multimedia_map:^TUIMultimediaTabPanelTab *(UITableView *v) {
|
||||
return [[TUIMultimediaTabPanelTab alloc] initWithName:[TUIMultimediaCommon localizedStringForKey:self->_bgmConfig[v.tag].name] icon:nil view:v];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (NSArray<TUIMultimediaBGM *> *)getBgmListByTableView:(UITableView *)v {
|
||||
return _bgmConfig[v.tag].bgmList;
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDataSource
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return [self getBgmListByTableView:tableView].count;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIMultimediaMusicCell *cell = [tableView dequeueReusableCellWithIdentifier:TUIMultimediaMusicCell.reuseIdentifier forIndexPath:indexPath];
|
||||
cell.music = [self getBgmListByTableView:tableView][indexPath.item];
|
||||
if (cell.music == _selectedBgm) {
|
||||
if (_switchBgm.on) {
|
||||
cell.state = TUIMultimediaMusicCellStateEnabled;
|
||||
} else {
|
||||
cell.state = TUIMultimediaMusicCellStateSelected;
|
||||
}
|
||||
} else {
|
||||
cell.state = TUIMultimediaMusicCellStateNormal;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
#pragma mark - UITableViewDelegate
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:NO];
|
||||
_selectedBgm = [self getBgmListByTableView:tableView][indexPath.item];
|
||||
if (!_switchBgm.on) {
|
||||
_switchBgm.on = YES;
|
||||
}
|
||||
[_delegate bgmEditViewValueChanged:self];
|
||||
for (UITableView *v in _tableViews) {
|
||||
[v reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)onSwitchOriginAudioChanged {
|
||||
[_delegate bgmEditViewValueChanged:self];
|
||||
}
|
||||
|
||||
- (void)onSwitchBGMChanged {
|
||||
[_delegate bgmEditViewValueChanged:self];
|
||||
for (UITableView *v in _tableViews) {
|
||||
[v reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Properties
|
||||
- (void)setBgmConfig:(NSArray<TUIMultimediaBGMGroup *> *)bgmConfig {
|
||||
_bgmConfig = bgmConfig;
|
||||
[self reloadConfig];
|
||||
}
|
||||
- (BOOL)originAudioEnabled {
|
||||
return _switchOriginAudio.on;
|
||||
}
|
||||
- (void)setOriginAudioEnabled:(BOOL)originAudioEnabled {
|
||||
_switchOriginAudio.on = originAudioEnabled;
|
||||
}
|
||||
|
||||
- (BOOL)bgmEnabled {
|
||||
return _switchBgm.on;
|
||||
}
|
||||
- (void)setBgmEnabled:(BOOL)bgmEnabled {
|
||||
_switchBgm.on = bgmEnabled;
|
||||
}
|
||||
|
||||
- (void)setClipDuration:(float)videoDuration {
|
||||
_clipDuration = videoDuration;
|
||||
}
|
||||
|
||||
@end
|
||||
18
TUIKit/TUIMultimediaPlugin/Pick/NSBundle+TUIImagePicker.h
Executable file
18
TUIKit/TUIMultimediaPlugin/Pick/NSBundle+TUIImagePicker.h
Executable 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
|
||||
|
||||
34
TUIKit/TUIMultimediaPlugin/Pick/NSBundle+TUIImagePicker.m
Executable file
34
TUIKit/TUIMultimediaPlugin/Pick/NSBundle+TUIImagePicker.m
Executable 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
|
||||
26
TUIKit/TUIMultimediaPlugin/Pick/TUIMultimediaAlbumPicker.h
Normal file
26
TUIKit/TUIMultimediaPlugin/Pick/TUIMultimediaAlbumPicker.h
Normal 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
|
||||
281
TUIKit/TUIMultimediaPlugin/Pick/TUIMultimediaAlbumPicker.m
Normal file
281
TUIKit/TUIMultimediaPlugin/Pick/TUIMultimediaAlbumPicker.m
Normal 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
|
||||
|
||||
107
TUIKit/TUIMultimediaPlugin/Pick/TUIMultimediaNavController.h
Normal file
107
TUIKit/TUIMultimediaPlugin/Pick/TUIMultimediaNavController.h
Normal 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
|
||||
698
TUIKit/TUIMultimediaPlugin/Pick/TUIMultimediaNavController.m
Normal file
698
TUIKit/TUIMultimediaPlugin/Pick/TUIMultimediaNavController.m
Normal 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
|
||||
16
TUIKit/TUIMultimediaPlugin/Pick/TUIPhotoPickerController.h
Normal file
16
TUIKit/TUIMultimediaPlugin/Pick/TUIPhotoPickerController.h
Normal 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
|
||||
927
TUIKit/TUIMultimediaPlugin/Pick/TUIPhotoPickerController.m
Executable file
927
TUIKit/TUIMultimediaPlugin/Pick/TUIPhotoPickerController.m
Executable 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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user