增加换肤功能

This commit is contained in:
启星
2025-08-14 10:07:49 +08:00
parent f6964c1e89
commit 4f9318d98e
8789 changed files with 978530 additions and 2 deletions

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