This commit is contained in:
启星
2025-08-12 14:27:12 +08:00
parent 9d18b353b1
commit 1bd5e77c45
8785 changed files with 978163 additions and 2 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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);
}

View 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

View 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

View 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

View 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
// devices 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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