提交
This commit is contained in:
17
TUIKit/TUIChat/BaseCell/TUIChatPopActionsView.h
Normal file
17
TUIKit/TUIChat/BaseCell/TUIChatPopActionsView.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TUIChatPopActionsView.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/6/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIChatPopActionsView : UIView
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
28
TUIKit/TUIChat/BaseCell/TUIChatPopActionsView.m
Normal file
28
TUIKit/TUIChat/BaseCell/TUIChatPopActionsView.m
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// TUIChatPopActionsView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2022/6/13.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIChatPopActionsView.h"
|
||||
|
||||
@implementation TUIChatPopActionsView
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
[self updateCorner];
|
||||
}
|
||||
- (void)updateCorner {
|
||||
UIRectCorner corner = UIRectCornerBottomLeft | UIRectCornerBottomRight;
|
||||
CGRect containerBounds = self.bounds;
|
||||
CGRect bounds = CGRectMake(containerBounds.origin.x, containerBounds.origin.y - 1, containerBounds.size.width, containerBounds.size.height);
|
||||
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:corner cornerRadii:CGSizeMake(5, 5)];
|
||||
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
|
||||
maskLayer.frame = self.bounds;
|
||||
maskLayer.path = maskPath.CGPath;
|
||||
self.layer.mask = maskLayer;
|
||||
}
|
||||
@end
|
||||
56
TUIKit/TUIChat/BaseCell/TUIChatPopMenu.h
Normal file
56
TUIKit/TUIChat/BaseCell/TUIChatPopMenu.h
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// TUIChatPopMenu.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIChatConfig.h"
|
||||
#import "TUIChatPopMenuDefine.h"
|
||||
#import <TIMCommon/TUIMessageCellData.h>
|
||||
#import <TIMCommon/TUIMessageCell.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^TUIChatPopMenuActionCallback)(void);
|
||||
|
||||
@interface TUIChatPopMenuAction : NSObject
|
||||
|
||||
@property(nonatomic, copy) NSString *title;
|
||||
@property(nonatomic, strong) UIImage *image;
|
||||
@property(nonatomic, copy) TUIChatPopMenuActionCallback callback;
|
||||
|
||||
/**
|
||||
* The higher the weight, the more prominent it is: audioPlayback 11000 Copy 10000, Forward 9000, Multiple Choice 8000, Quote 7000, Reply 5000, Withdraw 4000, Delete 3000.
|
||||
*/
|
||||
@property(nonatomic, assign) NSInteger weight;
|
||||
|
||||
- (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image weight:(NSInteger)weight callback:(TUIChatPopMenuActionCallback)callback;
|
||||
@end
|
||||
|
||||
typedef void (^TUIChatPopMenuHideCallback)(void);
|
||||
@interface TUIChatPopMenu : UIView
|
||||
@property(nonatomic, copy) TUIChatPopMenuHideCallback hideCallback;
|
||||
@property(nonatomic, copy) void (^reactClickCallback)(NSString *faceName);
|
||||
@property(nonatomic, weak) TUIMessageCellData *targetCellData;
|
||||
@property(nonatomic, weak) TUIMessageCell *targetCell;
|
||||
/**
|
||||
* TUIChatPopMenu has no emojiView by default. If you need a chatPopMenu with emojiView, use this initializer.
|
||||
*/
|
||||
- (instancetype)initWithEmojiView:(BOOL)hasEmojiView frame:(CGRect)frame;
|
||||
|
||||
@property(nonatomic, strong, readonly) UIView *emojiContainerView;
|
||||
@property(nonatomic, strong, readonly) UIView *containerView;
|
||||
|
||||
- (void)addAction:(TUIChatPopMenuAction *)action;
|
||||
- (void)removeAllAction;
|
||||
- (void)setArrawPosition:(CGPoint)point adjustHeight:(CGFloat)adjustHeight;
|
||||
- (void)showInView:(UIView *__nullable)window;
|
||||
- (void)layoutSubview;
|
||||
- (void)hideWithAnimation;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
555
TUIKit/TUIChat/BaseCell/TUIChatPopMenu.m
Normal file
555
TUIKit/TUIChat/BaseCell/TUIChatPopMenu.m
Normal file
@@ -0,0 +1,555 @@
|
||||
//
|
||||
// TUIChatPopMenu.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by harvy on 2021/11/30.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIChatPopMenu.h"
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import "TUIChatPopActionsView.h"
|
||||
#import <TIMCommon/TIMCommonMediator.h>
|
||||
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
|
||||
#import <TUICore/TUICore.h>
|
||||
#import "TUIFaceView.h"
|
||||
|
||||
#define maxColumns 5
|
||||
#define kContainerInsets UIEdgeInsetsMake(3, 0, 3, 0)
|
||||
#define kActionWidth 54
|
||||
#define kActionHeight 65
|
||||
#define kActionMargin 5
|
||||
#define kSepartorHeight 0.5
|
||||
#define kSepartorLRMargin 10
|
||||
#define kArrowSize CGSizeMake(15, 10)
|
||||
#define kEmojiHeight 44
|
||||
|
||||
@implementation TUIChatPopMenuAction
|
||||
|
||||
- (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image weight:(NSInteger)weight callback:(TUIChatPopMenuActionCallback)callback {
|
||||
if (self = [super init]) {
|
||||
self.title = title;
|
||||
self.image = image;
|
||||
self.weight = weight;
|
||||
self.callback = callback;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIChatPopMenu () <UIGestureRecognizerDelegate,V2TIMAdvancedMsgListener>
|
||||
|
||||
/**
|
||||
* emojiRecent view and emoji secondary page view
|
||||
*/
|
||||
@property(nonatomic, strong) UIView *emojiContainerView;
|
||||
|
||||
@property(nonatomic, strong) UIView *containerView;
|
||||
|
||||
@property(nonatomic, strong) NSMutableArray *actions;
|
||||
|
||||
@property(nonatomic, assign) CGPoint arrawPoint;
|
||||
|
||||
@property(nonatomic, assign) CGFloat adjustHeight;
|
||||
|
||||
@property(nonatomic, strong) NSMutableDictionary *actionCallback;
|
||||
|
||||
@property(nonatomic, strong) CAShapeLayer *arrowLayer;
|
||||
|
||||
@property(nonatomic, assign) CGFloat emojiHeight;
|
||||
|
||||
@property(nonatomic, strong) TUIChatPopActionsView *actionsView;
|
||||
|
||||
@property(nonatomic, assign) BOOL hasEmojiView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatPopMenu
|
||||
|
||||
- (void)addAction:(TUIChatPopMenuAction *)action {
|
||||
if (action) {
|
||||
[self.actions addObject:action];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeAllAction {
|
||||
[self.actions removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)setArrawPosition:(CGPoint)point adjustHeight:(CGFloat)adjustHeight {
|
||||
point = CGPointMake(point.x, point.y - NavBar_Height);
|
||||
self.arrawPoint = point;
|
||||
self.adjustHeight = adjustHeight;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEmojiView:(BOOL)hasEmojiView frame:(CGRect)frame {
|
||||
self.hasEmojiView = hasEmojiView;
|
||||
return [self initWithFrame:frame];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap:)];
|
||||
tap.delegate = self;
|
||||
pan.delegate = self;
|
||||
[self addGestureRecognizer:tap];
|
||||
[self addGestureRecognizer:pan];
|
||||
if ([self isAddEmojiView]) {
|
||||
self.emojiHeight = kEmojiHeight;
|
||||
}
|
||||
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(hideWithAnimation) name:@"kTUIChatPopMenuWillHideNotification" object:nil];
|
||||
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(hideWithAnimation) name:UIKeyboardWillChangeFrameNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onThemeChanged) name:TUIDidApplyingThemeChangedNotfication object:nil];
|
||||
[[V2TIMManager sharedInstance] addAdvancedMsgListener:self];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isAddEmojiView {
|
||||
return self.hasEmojiView && [TUIChatConfig defaultConfig].enablePopMenuEmojiReactAction;
|
||||
}
|
||||
|
||||
- (void)onTap:(UIGestureRecognizer *)tap {
|
||||
[self hideWithAnimation];
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
|
||||
if ([touch.view isDescendantOfView:self.emojiContainerView]) {
|
||||
return NO;
|
||||
}
|
||||
if ([touch.view isDescendantOfView:self.containerView]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (@available(iOS 17.0, *)) {
|
||||
CGPoint touchPoint = [touch locationInView:touch.view.nextResponder];
|
||||
CGRect frame = self.targetCell.frame;
|
||||
if (CGRectContainsPoint(frame, touchPoint)) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
if (@available(iOS 17.0, *)) {
|
||||
CGPoint touchPoint = [self.superview convertPoint:point fromView:self];
|
||||
CGRect frame = self.targetCell.frame;
|
||||
CGRect containerFrame = [self.superview convertRect:self.targetCell.container.frame fromView:self.targetCell];
|
||||
// CGRect popFrame1 = [self.superview convertRect:self.emojiContainerView.frame fromView:self];
|
||||
CGRect popFrame2 = [self.superview convertRect:self.containerView.frame fromView:self];
|
||||
if ( CGRectContainsPoint(popFrame2, touchPoint)) {
|
||||
return [super hitTest:point withEvent:event];
|
||||
}
|
||||
[self.superview convertRect:self.targetCell.container.frame fromView:self.targetCell];
|
||||
|
||||
if (CGRectContainsPoint(frame, touchPoint)) {
|
||||
if ([self.targetCell respondsToSelector:@selector(textView)]) {
|
||||
UITextView *textView = [self.targetCell valueForKey:@"textView"];
|
||||
if (CGRectContainsPoint(containerFrame,touchPoint)) {
|
||||
if (textView && [textView isKindOfClass:UITextView.class] && !textView.isSelectable) {
|
||||
[textView selectAll:self];
|
||||
}
|
||||
return textView;
|
||||
}else {
|
||||
if (textView && [textView isKindOfClass:UITextView.class]) {
|
||||
[textView selectAll:nil];
|
||||
[self hideWithAnimation];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
[self hideWithAnimation];
|
||||
}
|
||||
return [super hitTest:point withEvent:event];
|
||||
}
|
||||
return [super hitTest:point withEvent:event];
|
||||
}
|
||||
else {
|
||||
return [super hitTest:point withEvent:event];
|
||||
}
|
||||
}
|
||||
- (void)hideWithAnimation {
|
||||
[UIView animateWithDuration:0.3
|
||||
animations:^{
|
||||
self.alpha = 0;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
if (self.hideCallback) {
|
||||
self.hideCallback();
|
||||
}
|
||||
[self removeFromSuperview];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)hideByClickButton:(UIButton *)button callback:(void (^__nullable)(void))callback {
|
||||
[UIView animateWithDuration:0.3
|
||||
animations:^{
|
||||
self.alpha = 0;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
if (self.hideCallback) {
|
||||
self.hideCallback();
|
||||
}
|
||||
[self removeFromSuperview];
|
||||
}
|
||||
}];
|
||||
}
|
||||
- (void)showInView:(UIView *)window {
|
||||
if (window == nil) {
|
||||
window = UIApplication.sharedApplication.keyWindow;
|
||||
}
|
||||
|
||||
self.frame = window.bounds;
|
||||
[window addSubview:self];
|
||||
|
||||
[self layoutSubview];
|
||||
}
|
||||
|
||||
- (void)layoutSubview {
|
||||
self.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
self.layer.shadowRadius = 5;
|
||||
self.layer.shadowOpacity = 0.5;
|
||||
|
||||
[self updateActionByRank];
|
||||
|
||||
if ([self isAddEmojiView]) {
|
||||
[self prepareEmojiView];
|
||||
}
|
||||
|
||||
[self prepareContainerView];
|
||||
|
||||
if ([self isAddEmojiView]) {
|
||||
[self setupEmojiSubView];
|
||||
}
|
||||
|
||||
[self setupContainerPosition];
|
||||
|
||||
[self updateLayout];
|
||||
|
||||
if (isRTL()) {
|
||||
[self fitRTLViews];
|
||||
}
|
||||
|
||||
}
|
||||
- (void)fitRTLViews {
|
||||
if (self.actionsView) {
|
||||
for (UIView *subview in self.actionsView.subviews) {
|
||||
if ([subview respondsToSelector:@selector(resetFrameToFitRTL)]) {
|
||||
[subview resetFrameToFitRTL];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
- (void)updateActionByRank {
|
||||
NSArray *ageSortResultArray = [self.actions sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
|
||||
TUIChatPopMenuAction *per1 = obj1;
|
||||
TUIChatPopMenuAction *per2 = obj2;
|
||||
return per1.weight > per2.weight ? NSOrderedAscending : NSOrderedDescending;
|
||||
}];
|
||||
NSMutableArray *filterArray = [NSMutableArray arrayWithArray:ageSortResultArray];
|
||||
|
||||
self.actions = [NSMutableArray arrayWithArray:ageSortResultArray];
|
||||
}
|
||||
- (void)setupContainerPosition {
|
||||
/**
|
||||
* Calculate the coordinates and correct them, the default arrow points down
|
||||
*/
|
||||
CGFloat minTopBottomMargin = (Is_IPhoneX ? (100) : (0.0));
|
||||
CGFloat minLeftRightMargin = 50;
|
||||
CGFloat containerW = self.containerView.bounds.size.width;
|
||||
CGFloat containerH = self.containerView.bounds.size.height;
|
||||
CGFloat upContainerY = self.arrawPoint.y + self.adjustHeight + kArrowSize.height; // The containerY value when arrow points up
|
||||
|
||||
/**
|
||||
* The default arrow points down
|
||||
*/
|
||||
CGFloat containerX = self.arrawPoint.x - 0.5 * containerW;
|
||||
CGFloat containerY = self.arrawPoint.y - kArrowSize.height - containerH - StatusBar_Height - self.emojiHeight;
|
||||
BOOL top = NO; // The direction of arrow, here is down
|
||||
CGFloat arrawX = 0.5 * containerW;
|
||||
CGFloat arrawY = kArrowSize.height + containerH - 1.5;
|
||||
|
||||
/**
|
||||
* Corrected vertical coordinates
|
||||
*/
|
||||
if (containerY < minTopBottomMargin) {
|
||||
/**
|
||||
* At this time, the container is too high, and it is planned to adjust the direction of the arrow to upward.
|
||||
*/
|
||||
if (upContainerY + containerH + minTopBottomMargin > self.superview.bounds.size.height) {
|
||||
/**
|
||||
* After adjusting the upward arrow direction, it will cause the entire container to exceed the screen. At this time, the adjustment strategy is
|
||||
* changed to: keep the arrow direction downward and move self.arrawPoint
|
||||
*/
|
||||
top = NO;
|
||||
self.arrawPoint = CGPointMake(self.arrawPoint.x, self.arrawPoint.y - containerY);
|
||||
containerY = self.arrawPoint.y - kArrowSize.height - containerH;
|
||||
|
||||
} else {
|
||||
/**
|
||||
* Adjust the direction of the arrow to meet the requirements
|
||||
*/
|
||||
top = YES;
|
||||
self.arrawPoint = CGPointMake(self.arrawPoint.x, self.arrawPoint.y + self.adjustHeight - StatusBar_Height - 5);
|
||||
arrawY = -kArrowSize.height;
|
||||
containerY = self.arrawPoint.y + kArrowSize.height;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Corrected horizontal coordinates
|
||||
*/
|
||||
if (containerX < minLeftRightMargin) {
|
||||
/**
|
||||
* At this time, the container is too close to the left side of the screen and needs to move to the right
|
||||
*/
|
||||
CGFloat offset = (minLeftRightMargin - containerX);
|
||||
arrawX = arrawX - offset;
|
||||
containerX = containerX + offset;
|
||||
if (arrawX < 20) {
|
||||
arrawX = 20;
|
||||
}
|
||||
|
||||
} else if (containerX + containerW + minLeftRightMargin > self.bounds.size.width) {
|
||||
/**
|
||||
* At this time, the container is too close to the right side of the screen and needs to be moved to the left
|
||||
*/
|
||||
CGFloat offset = containerX + containerW + minLeftRightMargin - self.bounds.size.width;
|
||||
arrawX = arrawX + offset;
|
||||
containerX = containerX - offset;
|
||||
if (arrawX > containerW - 20) {
|
||||
arrawX = containerW - 20;
|
||||
}
|
||||
}
|
||||
|
||||
self.emojiContainerView.frame = CGRectMake(containerX, containerY, containerW, MAX(self.emojiHeight + containerH, 200));
|
||||
self.containerView.frame = CGRectMake(containerX, containerY + self.emojiHeight, containerW, containerH);
|
||||
|
||||
/**
|
||||
* Drawing arrow
|
||||
*/
|
||||
self.arrowLayer = [[CAShapeLayer alloc] init];
|
||||
self.arrowLayer.path = [self arrawPath:CGPointMake(arrawX, arrawY) directionTop:top].CGPath;
|
||||
self.arrowLayer.fillColor = TUIChatDynamicColor(@"chat_pop_menu_bg_color", @"#FFFFFF").CGColor;
|
||||
if (top) {
|
||||
if (self.emojiContainerView) {
|
||||
[self.emojiContainerView.layer addSublayer:self.arrowLayer];
|
||||
} else {
|
||||
[self.containerView.layer addSublayer:self.arrowLayer];
|
||||
}
|
||||
} else {
|
||||
[self.containerView.layer addSublayer:self.arrowLayer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)prepareEmojiView {
|
||||
if (self.emojiContainerView) {
|
||||
[self.emojiContainerView removeFromSuperview];
|
||||
self.emojiContainerView = nil;
|
||||
}
|
||||
|
||||
self.emojiContainerView = [[UIView alloc] init];
|
||||
[self addSubview:self.emojiContainerView];
|
||||
}
|
||||
- (void)prepareContainerView {
|
||||
if (self.containerView) {
|
||||
[self.containerView removeFromSuperview];
|
||||
self.containerView = nil;
|
||||
}
|
||||
self.containerView = [[UIView alloc] init];
|
||||
[self addSubview:self.containerView];
|
||||
|
||||
self.actionsView = [[TUIChatPopActionsView alloc] init];
|
||||
self.actionsView.backgroundColor = TUIChatDynamicColor(@"chat_pop_menu_bg_color", @"#FFFFFF");
|
||||
[self.containerView addSubview:self.actionsView];
|
||||
|
||||
int i = 0;
|
||||
for (TUIChatPopMenuAction *action in self.actions) {
|
||||
UIButton *actionButton = [self buttonWithAction:action tag:[self.actions indexOfObject:action]];
|
||||
[self.actionsView addSubview:actionButton];
|
||||
i++;
|
||||
if (i == maxColumns && i < self.actions.count) {
|
||||
UIView *separtorView = [[UIView alloc] init];
|
||||
separtorView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#39393B");
|
||||
separtorView.hidden = YES;
|
||||
[self.actionsView addSubview:separtorView];
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculating the size of container
|
||||
*/
|
||||
int rows = (self.actions.count % maxColumns == 0) ? (int)self.actions.count / maxColumns : (int)(self.actions.count / maxColumns) + 1;
|
||||
int columns = self.actions.count < maxColumns ? (int)self.actions.count : maxColumns;
|
||||
if ([self isAddEmojiView]) {
|
||||
columns = maxColumns;
|
||||
}
|
||||
CGFloat width = kActionWidth * columns + kActionMargin * (columns + 1) + kContainerInsets.left + kContainerInsets.right;
|
||||
CGFloat height = kActionHeight * rows + (rows - 1) * kSepartorHeight + kContainerInsets.top + kContainerInsets.bottom;
|
||||
|
||||
self.emojiContainerView.frame = CGRectMake(0, 0, width, self.emojiHeight + height);
|
||||
self.containerView.frame = CGRectMake(0, self.emojiHeight, width, height);
|
||||
}
|
||||
|
||||
- (void)setupEmojiSubView {
|
||||
[self setupEmojiRecentView];
|
||||
[self setupEmojiAdvanceView];
|
||||
}
|
||||
- (void)setupEmojiRecentView {
|
||||
NSDictionary *param = @{TUICore_TUIChatExtension_ChatPopMenuReactRecentView_Delegate : self};
|
||||
BOOL isRaiseEmojiExtensionSuccess = [TUICore raiseExtension:TUICore_TUIChatExtension_ChatPopMenuReactRecentView_ClassicExtensionID
|
||||
parentView:self.emojiContainerView
|
||||
param:param];
|
||||
if (!isRaiseEmojiExtensionSuccess) {
|
||||
self.emojiHeight = 0;
|
||||
}
|
||||
}
|
||||
- (void)setupEmojiAdvanceView {
|
||||
NSDictionary *param = @{TUICore_TUIChatExtension_ChatPopMenuReactRecentView_Delegate : self};
|
||||
[TUICore raiseExtension:TUICore_TUIChatExtension_ChatPopMenuReactDetailView_ClassicExtensionID parentView:self.emojiContainerView param:param];
|
||||
}
|
||||
|
||||
- (void)updateLayout {
|
||||
|
||||
self.actionsView.frame = CGRectMake(0, -0.5, self.containerView.frame.size.width, self.containerView.frame.size.height);
|
||||
|
||||
int columns = self.actions.count < maxColumns ? (int)self.actions.count : maxColumns;
|
||||
CGFloat containerWidth = kActionWidth * columns + kActionMargin * (columns + 1) + kContainerInsets.left + kContainerInsets.right;
|
||||
|
||||
int i = 0;
|
||||
int currentRow = 0;
|
||||
int currentColumn = 0;
|
||||
for (UIView *subView in self.actionsView.subviews) {
|
||||
if ([subView isKindOfClass:UIButton.class]) {
|
||||
currentRow = i / maxColumns;
|
||||
currentColumn = i % maxColumns;
|
||||
|
||||
CGFloat x = kContainerInsets.left + (currentColumn + 1) * kActionMargin + currentColumn * kActionWidth;
|
||||
CGFloat y = kContainerInsets.top + currentRow * kActionHeight + currentRow * kSepartorHeight;
|
||||
subView.frame = CGRectMake(x, y, kActionWidth, kActionHeight);
|
||||
|
||||
i++;
|
||||
} else {
|
||||
CGFloat y = (currentRow + 1) * kActionHeight + kContainerInsets.top;
|
||||
CGFloat width = containerWidth - 2 * kSepartorLRMargin - kContainerInsets.left - kContainerInsets.right;
|
||||
subView.frame = CGRectMake(kSepartorLRMargin, y, width, kSepartorHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (UIBezierPath *)arrawPath:(CGPoint)point directionTop:(BOOL)top {
|
||||
CGSize arrowSize = kArrowSize;
|
||||
UIBezierPath *arrowPath = [[UIBezierPath alloc] init];
|
||||
[arrowPath moveToPoint:point];
|
||||
if (top) {
|
||||
[arrowPath addLineToPoint:CGPointMake(point.x + arrowSize.width * 0.5, point.y + arrowSize.height)];
|
||||
[arrowPath addLineToPoint:CGPointMake(point.x - arrowSize.width * 0.5, point.y + arrowSize.height)];
|
||||
} else {
|
||||
[arrowPath addLineToPoint:CGPointMake(point.x + arrowSize.width * 0.5, point.y - arrowSize.height)];
|
||||
[arrowPath addLineToPoint:CGPointMake(point.x - arrowSize.width * 0.5, point.y - arrowSize.height)];
|
||||
}
|
||||
[arrowPath closePath];
|
||||
return arrowPath;
|
||||
}
|
||||
|
||||
- (UIButton *)buttonWithAction:(TUIChatPopMenuAction *)action tag:(NSInteger)tag {
|
||||
UIButton *actionButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[actionButton setTitleColor:TUIChatDynamicColor(@"chat_pop_menu_text_color", @"#444444")
|
||||
forState:UIControlStateNormal];
|
||||
actionButton.titleLabel.font = [UIFont systemFontOfSize:10.0];
|
||||
actionButton.titleLabel.numberOfLines = 2;
|
||||
actionButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
[actionButton setTitle:action.title forState:UIControlStateNormal];
|
||||
[actionButton setImage:action.image forState:UIControlStateNormal];
|
||||
actionButton.contentMode = UIViewContentModeScaleAspectFit;
|
||||
|
||||
[actionButton addTarget:self action:@selector(buttonHighlightedEnter:) forControlEvents:UIControlEventTouchDown];
|
||||
[actionButton addTarget:self action:@selector(buttonHighlightedEnter:) forControlEvents:UIControlEventTouchDragEnter];
|
||||
[actionButton addTarget:self action:@selector(buttonHighlightedExit:) forControlEvents:UIControlEventTouchDragExit];
|
||||
[actionButton addTarget:self action:@selector(onClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
actionButton.tag = tag;
|
||||
|
||||
CGSize imageSize = CGSizeMake(20, 20);
|
||||
CGSize titleSize = actionButton.titleLabel.frame.size;
|
||||
CGSize textSize = [actionButton.titleLabel.text sizeWithAttributes:@{NSFontAttributeName : actionButton.titleLabel.font}];
|
||||
CGSize frameSize = CGSizeMake(ceilf(textSize.width), ceilf(textSize.height));
|
||||
if (titleSize.width + 0.5 < frameSize.width) {
|
||||
titleSize.width = frameSize.width;
|
||||
}
|
||||
titleSize.width = MIN(titleSize.width, 48);
|
||||
CGFloat totalHeight = (imageSize.height + titleSize.height + 8);
|
||||
actionButton.imageEdgeInsets = UIEdgeInsetsMake(-(totalHeight - imageSize.height), 0.0, 0.0, -titleSize.width);
|
||||
actionButton.titleEdgeInsets = UIEdgeInsetsMake(0, -imageSize.width, -(totalHeight - titleSize.height), 0);
|
||||
|
||||
[self.actionCallback setObject:action.callback forKey:@(tag)];
|
||||
|
||||
return actionButton;
|
||||
}
|
||||
|
||||
- (void)buttonHighlightedEnter:(UIButton *)sender {
|
||||
sender.backgroundColor = TUIChatDynamicColor(@"", @"#006EFF19");
|
||||
}
|
||||
- (void)buttonHighlightedExit:(UIButton *)sender {
|
||||
sender.backgroundColor = [UIColor clearColor];
|
||||
}
|
||||
- (void)onClick:(UIButton *)button {
|
||||
if (![self.actionCallback.allKeys containsObject:@(button.tag)]) {
|
||||
[self hideWithAnimation];
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self hideByClickButton:button
|
||||
callback:^() {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
TUIChatPopMenuActionCallback callback = [strongSelf.actionCallback objectForKey:@(button.tag)];
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSMutableArray *)actions {
|
||||
if (_actions == nil) {
|
||||
_actions = [NSMutableArray array];
|
||||
}
|
||||
return _actions;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)actionCallback {
|
||||
if (_actionCallback == nil) {
|
||||
_actionCallback = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _actionCallback;
|
||||
}
|
||||
|
||||
// MARK: V2TIMAdvancedMsgListener
|
||||
- (void)onRecvMessageRevoked:(NSString *)msgID operateUser:(V2TIMUserFullInfo *)operateUser reason:(NSString *)reason {
|
||||
if ([msgID isEqualToString:self.targetCellData.msgID]) {
|
||||
[self hideWithAnimation];
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ThemeChanged
|
||||
- (void)applyBorderTheme {
|
||||
if (_arrowLayer) {
|
||||
_arrowLayer.fillColor = TUIChatDynamicColor(@"chat_pop_menu_bg_color", @"#FFFFFF").CGColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onThemeChanged {
|
||||
[self applyBorderTheme];
|
||||
}
|
||||
@end
|
||||
18
TUIKit/TUIChat/BaseCell/TUIChatPopMenuDefine.h
Normal file
18
TUIKit/TUIChat/BaseCell/TUIChatPopMenuDefine.h
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// TUIChatPopMenuDefine.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by cologne on 2023/11/22.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define TChatEmojiView_Margin 10
|
||||
#define TChatEmojiView_MarginTopBottom 17
|
||||
|
||||
#define TChatEmojiView_Padding 20
|
||||
#define TChatEmojiView_Page_Height 30
|
||||
#define TChatEmojiView_CollectionOffsetY 8
|
||||
#define TChatEmojiView_CollectionHeight 107
|
||||
|
||||
41
TUIKit/TUIChat/BaseCell/TUIChatShortcutMenuView.h
Normal file
41
TUIKit/TUIChat/BaseCell/TUIChatShortcutMenuView.h
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// TUIChatShortcutMenuView.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by Tencent on 2023/6/29.
|
||||
// Copyright © 2024 Tencent. All rights reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIChatShortcutMenuCellData : NSObject
|
||||
@property (nonatomic, strong) NSString *text;
|
||||
@property (nonatomic, assign) SEL cselector;
|
||||
@property (nonatomic, strong) id target;
|
||||
|
||||
@property (nonatomic, strong) UIColor *textColor;
|
||||
@property (nonatomic, strong) UIColor *backgroundColor;
|
||||
@property (nonatomic, strong) UIFont *textFont;
|
||||
@property (nonatomic, strong) UIColor *borderColor;
|
||||
@property (nonatomic, assign) CGFloat borderWidth;
|
||||
@property (nonatomic, assign) CGFloat cornerRadius;
|
||||
|
||||
- (CGSize)calcSize;
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIChatShortcutMenuCell : UICollectionViewCell
|
||||
@property (nonatomic, strong) UIButton *button;
|
||||
@property (nonatomic, strong) TUIChatShortcutMenuCellData *cellData;
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIChatShortcutMenuView : UIView
|
||||
@property (nonatomic, assign) CGFloat viewHeight;
|
||||
@property (nonatomic, assign) CGFloat itemHorizontalSpacing;
|
||||
- (instancetype)initWithDataSource:(NSArray *)source;
|
||||
- (void)updateFrame;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
182
TUIKit/TUIChat/BaseCell/TUIChatShortcutMenuView.m
Normal file
182
TUIKit/TUIChat/BaseCell/TUIChatShortcutMenuView.m
Normal file
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// TUIChatShortcutMenuView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by Tencent on 2023/6/29.
|
||||
// Copyright © 2024 Tencent. All rights reserved.
|
||||
|
||||
#import "TUIChatShortcutMenuView.h"
|
||||
#import <TUICore/TUICore.h>
|
||||
#import <TUICore/TUIDefine.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@implementation TUIChatShortcutMenuCellData
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.textColor = [UIColor tui_colorWithHex:@"#8F959E"];
|
||||
self.textFont = [UIFont systemFontOfSize:14];
|
||||
self.backgroundColor = [UIColor tui_colorWithHex:@"#F6F7F9"];
|
||||
self.cornerRadius = 16;
|
||||
self.borderColor = [UIColor tui_colorWithHex:@"#C5CBD4"];
|
||||
self.borderWidth = 1.0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)calcSize {
|
||||
return [self calcMenuCellButtonSize:self.text];
|
||||
}
|
||||
|
||||
- (CGSize)calcMenuCellButtonSize:(NSString *)title {
|
||||
CGFloat margin = 28;
|
||||
CGRect rect = [title boundingRectWithSize:CGSizeMake(MAXFLOAT, 32)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
||||
attributes:@{ NSFontAttributeName : self.textFont}
|
||||
context:nil];
|
||||
return CGSizeMake(rect.size.width + margin, 32);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TUIChatShortcutMenuCell()
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatShortcutMenuCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.button = [UIButton new];
|
||||
[self addSubview:self.button];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillWithData:(TUIChatShortcutMenuCellData *)cellData {
|
||||
self.cellData = cellData;
|
||||
[self.button setTitle:cellData.text forState:UIControlStateNormal];
|
||||
[self.button addTarget:cellData.target action:cellData.cselector forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
self.button.layer.cornerRadius = self.cellData.cornerRadius;
|
||||
self.button.titleLabel.font = self.cellData.textFont;
|
||||
self.button.backgroundColor = self.cellData.backgroundColor;
|
||||
[self.button setTitleColor:self.cellData.textColor forState:UIControlStateNormal];
|
||||
self.button.layer.borderWidth = self.cellData.borderWidth;
|
||||
self.button.layer.borderColor = self.cellData.borderColor.CGColor;
|
||||
|
||||
[self updateConstraints];
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[super updateConstraints];
|
||||
|
||||
CGSize size = [self.cellData calcSize];
|
||||
[self.button mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(12);
|
||||
make.centerY.mas_equalTo(self.mas_centerY);
|
||||
make.width.mas_equalTo(size.width);
|
||||
make.height.mas_equalTo(size.height);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TUIChatShortcutMenuView() <UICollectionViewDelegate, UICollectionViewDataSource>
|
||||
|
||||
@property (nonatomic, strong) UICollectionView *collectionView;
|
||||
@property (nonatomic, strong) NSMutableArray *dataSource;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIChatShortcutMenuView
|
||||
|
||||
- (instancetype)initWithDataSource:(NSArray *)source {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.dataSource = [source mutableCopy];
|
||||
self.backgroundColor = [UIColor tui_colorWithHex:@"#EBF0F6"];
|
||||
[self addSubview:self.collectionView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
- (void)updateFrame {
|
||||
self.mm_left(0).mm_top(0).mm_width(Screen_Width).mm_height(self.viewHeight > 0 ? self.viewHeight : 46);
|
||||
self.collectionView.mm_fill();
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
- (UICollectionView *)collectionView {
|
||||
if (!_collectionView) {
|
||||
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout];
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.dataSource = self;
|
||||
_collectionView.scrollEnabled = YES;
|
||||
_collectionView.backgroundColor = [UIColor clearColor];
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
[_collectionView registerClass:[TUIChatShortcutMenuCell class] forCellWithReuseIdentifier:@"menuCell"];
|
||||
}
|
||||
return _collectionView;
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource & Delegate
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView
|
||||
numberOfItemsInSection:(NSInteger)section {
|
||||
return self.dataSource.count;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
|
||||
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIChatShortcutMenuCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"menuCell" forIndexPath:indexPath];
|
||||
TUIChatShortcutMenuCellData *cellData = self.dataSource[indexPath.row];
|
||||
[cell fillWithData:cellData];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIChatShortcutMenuCellData *cellData = self.dataSource[indexPath.row];
|
||||
return CGSizeMake([cellData calcSize].width + 12, [cellData calcSize].height);
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
insetForSectionAtIndex:(NSInteger)section {
|
||||
return UIEdgeInsetsMake(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout*)collectionViewLayout
|
||||
minimumLineSpacingForSectionAtIndex:(NSInteger)section {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView
|
||||
shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
25
TUIKit/TUIChat/BaseCell/TUIFaceSegementScrollView.h
Normal file
25
TUIKit/TUIChat/BaseCell/TUIFaceSegementScrollView.h
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// TUIFaceSegementScrollView.h
|
||||
// TUIEmojiPlugin
|
||||
//
|
||||
// Created by wyl on 2023/11/15.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIFaceView.h"
|
||||
#import "TUIFaceVerticalView.h"
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@class TUIFaceGroup;
|
||||
|
||||
@interface TUIFaceSegementScrollView : UIView
|
||||
@property(nonatomic, copy) void(^onScrollCallback)(NSInteger indexPage);
|
||||
@property(strong, nonatomic) UIScrollView *pageScrollView;
|
||||
- (void)setItems:(NSArray<TUIFaceGroup *> *)items delegate:(id <TUIFaceVerticalViewDelegate>) delegate;
|
||||
- (void)updateContainerView;
|
||||
- (void)setPageIndex:(NSInteger)index;
|
||||
- (void)setAllFloatCtrlViewAllowSendSwitch:(BOOL)isAllow;
|
||||
- (void)updateRecentView;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
129
TUIKit/TUIChat/BaseCell/TUIFaceSegementScrollView.m
Normal file
129
TUIKit/TUIChat/BaseCell/TUIFaceSegementScrollView.m
Normal file
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// TUIFaceSegementScrollView.m
|
||||
// TUIEmojiPlugin
|
||||
//
|
||||
// Created by wyl on 2023/11/15.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFaceSegementScrollView.h"
|
||||
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TIMCommon/TUIFitButton.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TIMCommon/TIMCommonMediator.h>
|
||||
#import <TIMCommon/TUIEmojiMeditorProtocol.h>
|
||||
|
||||
@interface TUIFaceSegementScrollView () <UIScrollViewDelegate>
|
||||
|
||||
@property(nonatomic, strong) NSArray<TUIFaceGroup *> *items;
|
||||
|
||||
@property(nonatomic, strong) NSMutableArray<TUIFaceVerticalView*> * viewArray;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TUIFaceSegementScrollView
|
||||
|
||||
|
||||
- (void)setItems:(NSArray<TUIFaceGroup *> *)items delegate:(id <TUIFaceViewDelegate>) delegate {
|
||||
_items = items;
|
||||
for (UIView *view in self.pageScrollView.subviews) {
|
||||
if (view) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
[self.viewArray removeAllObjects];
|
||||
|
||||
for (int i = 0; i < items.count; i++) {
|
||||
TUIFaceVerticalView* faceView = [[TUIFaceVerticalView alloc] initWithFrame:CGRectMake(0,
|
||||
0, self.frame.size.width, self.pageScrollView.frame.size.height)];
|
||||
TUIFaceGroup *indexGroup = items[i];
|
||||
if (indexGroup.recentGroup) {
|
||||
[faceView setData:[NSMutableArray arrayWithArray:@[indexGroup.recentGroup,indexGroup]]];
|
||||
}
|
||||
else {
|
||||
[faceView setData:[NSMutableArray arrayWithArray:@[indexGroup]]];
|
||||
}
|
||||
|
||||
faceView.frame = CGRectMake(i * self.frame.size.width, 0, self.frame.size.width, self.pageScrollView.frame.size.height);
|
||||
faceView.delegate = delegate;
|
||||
[self.pageScrollView addSubview:faceView];
|
||||
[self.viewArray addObject:faceView];
|
||||
}
|
||||
self.pageScrollView.contentSize = CGSizeMake(self.viewArray.count * self.frame.size.width, self.pageScrollView.frame.size.height);
|
||||
if (isRTL()) {
|
||||
_pageScrollView.transform = CGAffineTransformMakeRotation(M_PI);
|
||||
NSArray *subViews = _pageScrollView.subviews;
|
||||
for (UIView *subView in subViews) {
|
||||
subView.transform = CGAffineTransformMakeRotation(M_PI);
|
||||
}
|
||||
}
|
||||
}
|
||||
- (void)updateContainerView {
|
||||
self.pageScrollView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
|
||||
|
||||
for (int i = 0; i < self.viewArray.count; i++) {
|
||||
TUIFaceVerticalView* view = self.viewArray[i];
|
||||
view.frame = CGRectMake(i * self.pageScrollView.frame.size.width, 0, self.pageScrollView.frame.size.width, self.pageScrollView.frame.size.height);
|
||||
}
|
||||
|
||||
self.pageScrollView.contentSize = CGSizeMake(self.viewArray.count * self.frame.size.width, self.pageScrollView.frame.size.height);
|
||||
}
|
||||
|
||||
|
||||
- (UIScrollView *)pageScrollView {
|
||||
if (_pageScrollView == nil) {
|
||||
_pageScrollView = [[UIScrollView alloc]
|
||||
initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
|
||||
_pageScrollView.backgroundColor = [UIColor clearColor];
|
||||
_pageScrollView.delegate = self;
|
||||
_pageScrollView.showsVerticalScrollIndicator = NO;
|
||||
_pageScrollView.showsHorizontalScrollIndicator = NO;
|
||||
_pageScrollView.bounces = NO;
|
||||
_pageScrollView.scrollsToTop = NO;
|
||||
_pageScrollView.pagingEnabled = YES;
|
||||
[self addSubview:_pageScrollView];
|
||||
}
|
||||
return _pageScrollView;
|
||||
}
|
||||
- (NSMutableArray<TUIFaceVerticalView *> *)viewArray {
|
||||
if(!_viewArray) {
|
||||
_viewArray = [NSMutableArray arrayWithCapacity:3];
|
||||
}
|
||||
return _viewArray;
|
||||
}
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||||
if (scrollView == _pageScrollView) {
|
||||
int p = _pageScrollView.contentOffset.x / self.frame.size.width;
|
||||
if (self.onScrollCallback){
|
||||
self.onScrollCallback(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPageIndex:(NSInteger)index {
|
||||
CGPoint p = CGPointMake(_pageScrollView.frame.size.width * index,0);
|
||||
[_pageScrollView setContentOffset:p animated:NO];
|
||||
}
|
||||
|
||||
- (void)setAllFloatCtrlViewAllowSendSwitch:(BOOL)isAllow {
|
||||
for (TUIFaceVerticalView *view in self.viewArray) {
|
||||
[view setFloatCtrlViewAllowSendSwitch:isAllow];
|
||||
}
|
||||
}
|
||||
- (void)updateRecentView {
|
||||
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
|
||||
TUIFaceVerticalView* faceView = self.viewArray[0];
|
||||
TUIFaceGroup *indexGroup = self.items[0];
|
||||
indexGroup.recentGroup = [service getChatPopMenuRecentQueue];
|
||||
indexGroup.recentGroup.rowCount = 1;
|
||||
indexGroup.recentGroup.itemCountPerRow = 8;
|
||||
indexGroup.recentGroup.groupName = TIMCommonLocalizableString(TUIChatFaceGroupRecentEmojiName);
|
||||
if (indexGroup.isNeedAddInInputBar && indexGroup.recentGroup) {
|
||||
[faceView setData:[NSMutableArray arrayWithArray:@[indexGroup.recentGroup,indexGroup]]];
|
||||
}
|
||||
else {
|
||||
[faceView setData:[NSMutableArray arrayWithArray:@[indexGroup]]];
|
||||
}
|
||||
}
|
||||
@end
|
||||
71
TUIKit/TUIChat/BaseCell/TUIFaceVerticalView.h
Normal file
71
TUIKit/TUIChat/BaseCell/TUIFaceVerticalView.h
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// TUIFaceVerticalView.h
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2023/11/16.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIFaceView.h"
|
||||
@class TUIFaceVerticalView;
|
||||
@protocol TUIFaceVerticalViewDelegate <TUIFaceViewDelegate>
|
||||
- (void)faceViewClickSendMessageBtn;
|
||||
@end
|
||||
|
||||
@interface TUIFaceVerticalView : UIView
|
||||
|
||||
/**
|
||||
* Line view
|
||||
* The separtor which distinguish emoticons from other views
|
||||
*/
|
||||
@property(nonatomic, strong) UIView *lineView;
|
||||
|
||||
/**
|
||||
* The collectionView of emoticon view
|
||||
* Contains multiple lines of expressions, and cooperates with faceFlowLayout for flexible and unified view layout.
|
||||
*/
|
||||
@property(nonatomic, strong) UICollectionView *faceCollectionView;
|
||||
|
||||
/**
|
||||
* The flow layout of @faceCollectionView
|
||||
* Cooperating with faceCollectionView to make the expression view more beautiful. Supported setting layout direction, line spacing, cell spacing, etc.
|
||||
*/
|
||||
@property(nonatomic, strong) UICollectionViewFlowLayout *faceFlowLayout;
|
||||
|
||||
/**
|
||||
*
|
||||
* The data of @faceView
|
||||
* The object stored in this NSMutableArray is TUIFaceGroup, that is, the expression group.
|
||||
*/
|
||||
@property(nonatomic, strong, readonly) NSMutableArray *faceGroups;
|
||||
@property(nonatomic, strong, readonly) NSMutableArray *sectionIndexInGroup;
|
||||
@property(nonatomic, strong, readonly) NSMutableArray *groupIndexInSection;
|
||||
@property(nonatomic, strong, readonly) NSMutableDictionary *itemIndexs;
|
||||
@property(nonatomic, strong, readonly) UIView *floatCtrlView;
|
||||
|
||||
/**
|
||||
*
|
||||
* Delegate variable, delegated
|
||||
* Need to implement the functionality required in the @TUIFaceVerticalViewDelegate protocol.
|
||||
*/
|
||||
@property(nonatomic, weak) id<TUIFaceVerticalViewDelegate> delegate;
|
||||
|
||||
/**
|
||||
* Swipe to the specified expression group.
|
||||
* Switch the emoticon group according to the subscript of the emoticon group clicked by the user.
|
||||
*
|
||||
* @param index The index of the destination group, starting from 0.
|
||||
*/
|
||||
- (void)scrollToFaceGroupIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
* Setting data
|
||||
* Used to initialize TUIFaceView or update data in faceView when needed.
|
||||
*
|
||||
* @param data The data that needs to be set (TUIFaceGroup). The object stored in this NSMutableArray is TUIFaceGroup, that is, the emoticon group.
|
||||
*/
|
||||
- (void)setData:(NSMutableArray *)data;
|
||||
|
||||
- (void)setFloatCtrlViewAllowSendSwitch:(BOOL)isAllow;
|
||||
@end
|
||||
410
TUIKit/TUIChat/BaseCell/TUIFaceVerticalView.m
Normal file
410
TUIKit/TUIChat/BaseCell/TUIFaceVerticalView.m
Normal file
@@ -0,0 +1,410 @@
|
||||
//
|
||||
// TUIFaceVerticalView.m
|
||||
// TUIChat
|
||||
//
|
||||
// Created by wyl on 2023/11/16.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFaceVerticalView.h"
|
||||
|
||||
@interface TUIFaceVerticalView () <UICollectionViewDelegate,
|
||||
UICollectionViewDataSource,
|
||||
UICollectionViewDelegateFlowLayout,
|
||||
UIPopoverPresentationControllerDelegate>
|
||||
@property(nonatomic, strong) NSMutableArray *sectionIndexInGroup;
|
||||
@property(nonatomic, strong) NSMutableArray *groupIndexInSection;
|
||||
@property(nonatomic, strong) NSMutableDictionary *itemIndexs;
|
||||
@property(nonatomic, assign) NSInteger sectionCount;
|
||||
@property(nonatomic, assign) NSInteger curGroupIndex;
|
||||
|
||||
@property(nonatomic, strong) UIView *floatCtrlView;
|
||||
@property(nonatomic, strong) UIButton *sendButton;
|
||||
@property(nonatomic, strong) UIButton *deleteButton;
|
||||
|
||||
//preview
|
||||
@property (nonatomic, strong) UIImageView *dispalyView;
|
||||
@property (nonatomic, strong) UIImageView *dispalyImage;
|
||||
@property (nonatomic, assign) BOOL hasPreViewShow;
|
||||
@end
|
||||
|
||||
@implementation TUIFaceVerticalView
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
[self defaultLayout];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = TUIChatDynamicColor(@"chat_input_controller_bg_color", @"#EBF0F6");
|
||||
|
||||
_faceFlowLayout = [[TUICollectionRTLFitFlowLayout alloc] init];
|
||||
_faceFlowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
_faceFlowLayout.minimumLineSpacing = TFaceView_Margin;
|
||||
_faceFlowLayout.minimumInteritemSpacing = TFaceView_Margin;
|
||||
_faceFlowLayout.sectionInset = UIEdgeInsetsMake(0, TFaceView_Page_Padding, 0, TFaceView_Page_Padding);
|
||||
|
||||
_faceCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_faceFlowLayout];
|
||||
[_faceCollectionView registerClass:[TUIFaceCell class] forCellWithReuseIdentifier:TFaceCell_ReuseId];
|
||||
[_faceCollectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerView"];
|
||||
_faceCollectionView.collectionViewLayout = _faceFlowLayout;
|
||||
_faceCollectionView.pagingEnabled = NO;
|
||||
_faceCollectionView.delegate = self;
|
||||
_faceCollectionView.dataSource = self;
|
||||
_faceCollectionView.showsHorizontalScrollIndicator = NO;
|
||||
_faceCollectionView.showsVerticalScrollIndicator = NO;
|
||||
_faceCollectionView.backgroundColor = self.backgroundColor;
|
||||
_faceCollectionView.alwaysBounceVertical = YES;
|
||||
[self addSubview:_faceCollectionView];
|
||||
|
||||
_lineView = [[UIView alloc] init];
|
||||
_lineView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
|
||||
[self addSubview:_lineView];
|
||||
|
||||
[self setupfloatCtrlView];
|
||||
}
|
||||
- (void)setupfloatCtrlView {
|
||||
_floatCtrlView = [[UIView alloc] init];
|
||||
[self addSubview:_floatCtrlView];
|
||||
_sendButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[self.sendButton setTitle:TIMCommonLocalizableString(Send)forState:UIControlStateNormal];
|
||||
_sendButton.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
[self.sendButton addTarget:self action:@selector(didSelectSendButton:) forControlEvents:UIControlEventTouchUpInside];
|
||||
self.sendButton.backgroundColor = TIMCommonDynamicColor(@"", @"#0069F6");
|
||||
self.sendButton.layer.cornerRadius = 2;
|
||||
[_floatCtrlView addSubview:self.sendButton];
|
||||
|
||||
_deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_deleteButton setImage:[UIImage imageWithContentsOfFile:TUIChatFaceImagePath(@"del_normal")] forState:UIControlStateNormal];
|
||||
[_deleteButton setImageEdgeInsets:UIEdgeInsetsMake(5, 5, 5, 5)];
|
||||
_deleteButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_deleteButton.layer.cornerRadius = 2;
|
||||
[_deleteButton addTarget:self action:@selector(didSelectDeleteButton:) forControlEvents:UIControlEventTouchUpInside];
|
||||
_deleteButton.backgroundColor = [UIColor whiteColor];
|
||||
[_floatCtrlView addSubview:_deleteButton];
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self defaultLayout];
|
||||
}
|
||||
|
||||
- (void)defaultLayout {
|
||||
_lineView.frame = CGRectMake(0, 0, self.frame.size.width, TLine_Heigh);
|
||||
[_faceCollectionView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.mas_equalTo(self);
|
||||
}];
|
||||
|
||||
[_floatCtrlView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.mas_trailing).mas_offset(-16);
|
||||
make.bottom.mas_equalTo(self.mas_bottom).mas_offset(20);
|
||||
make.height.mas_equalTo(88);
|
||||
make.leading.mas_equalTo(self.deleteButton.mas_leading);
|
||||
}];
|
||||
|
||||
[self.sendButton mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.floatCtrlView.mas_trailing);
|
||||
make.top.mas_equalTo(self.floatCtrlView);
|
||||
make.height.mas_equalTo(30);
|
||||
make.width.mas_equalTo(50);
|
||||
}];
|
||||
[self.deleteButton mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.trailing.mas_equalTo(self.sendButton.mas_leading).mas_offset(-10);
|
||||
make.top.mas_equalTo(self.floatCtrlView);
|
||||
make.height.mas_equalTo(30);
|
||||
make.width.mas_equalTo(50);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setData:(NSMutableArray *)data {
|
||||
_faceGroups = data;
|
||||
[self defaultLayout];
|
||||
|
||||
_sectionIndexInGroup = [NSMutableArray array];
|
||||
_groupIndexInSection = [NSMutableArray array];
|
||||
_itemIndexs = [NSMutableDictionary dictionary];
|
||||
|
||||
NSInteger sectionIndex = 0;
|
||||
for (NSInteger groupIndex = 0; groupIndex < _faceGroups.count; ++groupIndex) {
|
||||
TUIFaceGroup *group = _faceGroups[groupIndex];
|
||||
[_sectionIndexInGroup addObject:@(sectionIndex)];
|
||||
int itemCount = group.faces.count;
|
||||
int sectionCount = ceil(group.faces.count * 1.0 / itemCount);
|
||||
for (int sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex) {
|
||||
[_groupIndexInSection addObject:@(groupIndex)];
|
||||
}
|
||||
sectionIndex += sectionCount;
|
||||
}
|
||||
_sectionCount = sectionIndex;
|
||||
|
||||
for (NSInteger curSection = 0; curSection < _sectionCount; ++curSection) {
|
||||
NSNumber *groupIndex = _groupIndexInSection[curSection];
|
||||
NSNumber *groupSectionIndex = _sectionIndexInGroup[groupIndex.integerValue];
|
||||
TUIFaceGroup *face = _faceGroups[groupIndex.integerValue];
|
||||
NSInteger itemCount = face.faces.count;
|
||||
NSInteger groupSection = curSection - groupSectionIndex.integerValue;
|
||||
for (NSInteger itemIndex = 0; itemIndex < itemCount; ++itemIndex) {
|
||||
// transpose line/row
|
||||
NSInteger reIndex = itemIndex;
|
||||
[_itemIndexs setObject:@(reIndex) forKey:[NSIndexPath indexPathForRow:itemIndex inSection:curSection]];
|
||||
}
|
||||
}
|
||||
|
||||
_curGroupIndex = 0;
|
||||
|
||||
[_faceCollectionView reloadData];
|
||||
|
||||
TUIFaceGroup *group = _faceGroups[0];
|
||||
if (!group.isNeedAddInInputBar) {
|
||||
_floatCtrlView.hidden = YES;
|
||||
}
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self adjustEmotionsAlpha];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
|
||||
return _sectionCount;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
int groupIndex = [_groupIndexInSection[section] intValue];
|
||||
TUIFaceGroup *group = _faceGroups[groupIndex];
|
||||
return group.faces.count;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIFaceCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:TFaceCell_ReuseId forIndexPath:indexPath];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
__weak typeof(cell) weakCell = cell;
|
||||
cell.longPressCallback = ^(UILongPressGestureRecognizer * _Nonnull recognizer) {
|
||||
if (weakSelf.hasPreViewShow) {
|
||||
return;
|
||||
}
|
||||
[self showDisplayView:0 display_y:0 targetView:[UIApplication sharedApplication].keyWindow.rootViewController.view faceCell:weakCell];
|
||||
};
|
||||
int groupIndex = [_groupIndexInSection[indexPath.section] intValue];
|
||||
TUIFaceGroup *group = _faceGroups[groupIndex];
|
||||
int itemCount = group.faces.count;
|
||||
|
||||
NSNumber *index = [_itemIndexs objectForKey:indexPath];
|
||||
if (index.integerValue < group.faces.count) {
|
||||
TUIFaceCellData *data = group.faces[index.integerValue];
|
||||
[cell setData:data];
|
||||
} else {
|
||||
[cell setData:nil];
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
int groupIndex = [_groupIndexInSection[indexPath.section] intValue];
|
||||
TUIFaceGroup *faces = _faceGroups[groupIndex];
|
||||
NSNumber *index = [_itemIndexs objectForKey:indexPath];
|
||||
if (index.integerValue < faces.faces.count) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(faceView:didSelectItemAtIndexPath:)]) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index.integerValue inSection:groupIndex];
|
||||
[_delegate faceView:self didSelectItemAtIndexPath:indexPath];
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
int groupIndex = [_groupIndexInSection[indexPath.section] intValue];
|
||||
TUIFaceGroup *group = _faceGroups[groupIndex];
|
||||
CGFloat width = (self.frame.size.width - TFaceView_Page_Padding * 2 - TFaceView_Margin * (group.itemCountPerRow - 1)) / group.itemCountPerRow;
|
||||
CGFloat height = width + TFaceView_Margin;
|
||||
return CGSizeMake(width, height);
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
referenceSizeForHeaderInSection:(NSInteger)section {
|
||||
TUIFaceGroup *group = _faceGroups[section];
|
||||
if (group.groupName.length > 0) {
|
||||
return CGSizeMake(self.frame.size.width, 20);
|
||||
}
|
||||
else {
|
||||
return CGSizeMake(self.frame.size.width, 0);
|
||||
}
|
||||
}
|
||||
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
|
||||
viewForSupplementaryElementOfKind:(NSString *)kind
|
||||
atIndexPath:(NSIndexPath *)indexPath {
|
||||
UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
|
||||
withReuseIdentifier:@"headerView"
|
||||
forIndexPath:indexPath];
|
||||
|
||||
for (UIView *view in headerView.subviews) {
|
||||
if (view) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
UILabel *view = [[UILabel alloc]initWithFrame:CGRectMake(TFaceView_Page_Padding, 0, self.frame.size.width, 17)];
|
||||
view.font = [UIFont systemFontOfSize:12];
|
||||
view.textColor = TIMCommonDynamicColor(@"", @"#444444");
|
||||
[headerView addSubview:view];
|
||||
TUIFaceGroup *group = _faceGroups[indexPath.section];
|
||||
if (group.groupName.length > 0 ){
|
||||
view.text = group.groupName;
|
||||
}
|
||||
return headerView;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
NSInteger curSection = round(scrollView.contentOffset.x / scrollView.frame.size.width);
|
||||
if (curSection >= _groupIndexInSection.count) {
|
||||
return;
|
||||
}
|
||||
NSNumber *groupIndex = _groupIndexInSection[curSection];
|
||||
NSNumber *startSection = _sectionIndexInGroup[groupIndex.integerValue];
|
||||
if (_curGroupIndex != groupIndex.integerValue) {
|
||||
_curGroupIndex = groupIndex.integerValue;
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(faceView:scrollToFaceGroupIndex:)]) {
|
||||
[_delegate faceView:self scrollToFaceGroupIndex:_curGroupIndex];
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollView == self.faceCollectionView) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self adjustEmotionsAlpha];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollToFaceGroupIndex:(NSInteger)index {
|
||||
if (index > _sectionIndexInGroup.count) {
|
||||
return;
|
||||
}
|
||||
NSNumber *start = _sectionIndexInGroup[index];
|
||||
NSInteger curSection = ceil(_faceCollectionView.contentOffset.x / _faceCollectionView.frame.size.width);
|
||||
if (curSection > start.integerValue && curSection < start.integerValue ) {
|
||||
return;
|
||||
}
|
||||
CGRect rect =
|
||||
CGRectMake(start.integerValue * _faceCollectionView.frame.size.width, 0, _faceCollectionView.frame.size.width, _faceCollectionView.frame.size.height);
|
||||
[_faceCollectionView scrollRectToVisible:rect animated:NO];
|
||||
[self scrollViewDidScroll:_faceCollectionView];
|
||||
}
|
||||
#pragma mark - floatCtrlView
|
||||
- (void)adjustEmotionsAlpha {
|
||||
if (self.floatCtrlView.isHidden) {
|
||||
return;
|
||||
}
|
||||
CGRect buttonGruopRect = self.floatCtrlView.frame;
|
||||
CGRect floatingRect = [self.faceCollectionView convertRect:buttonGruopRect fromView:self];
|
||||
for (UICollectionViewCell *visibleCell in self.faceCollectionView.visibleCells) {
|
||||
CGRect cellInCollection = [self.faceCollectionView convertRect:visibleCell.frame toView:self.faceCollectionView];
|
||||
BOOL ischongdie = CGRectIntersectsRect(floatingRect,cellInCollection);
|
||||
if(ischongdie){
|
||||
CGPoint emojiCenterPoint = CGPointMake(CGRectGetMidX(cellInCollection), CGRectGetMidY(cellInCollection));
|
||||
BOOL containsHalf = CGRectContainsPoint(floatingRect,emojiCenterPoint);
|
||||
if (containsHalf) {
|
||||
visibleCell.alpha = 0;
|
||||
} else {
|
||||
visibleCell.alpha = 0.5;
|
||||
}
|
||||
}
|
||||
else {
|
||||
visibleCell.alpha = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFloatCtrlViewAllowSendSwitch:(BOOL)isAllow {
|
||||
if (isAllow) {
|
||||
self.deleteButton.enabled = YES;
|
||||
self.sendButton.enabled = YES;
|
||||
self.deleteButton.alpha = 1;
|
||||
self.sendButton.alpha = 1;
|
||||
}
|
||||
else {
|
||||
self.deleteButton.enabled = NO;
|
||||
self.sendButton.enabled = NO;
|
||||
self.deleteButton.alpha = 0.5;
|
||||
self.sendButton.alpha = 0.5;
|
||||
}
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self adjustEmotionsAlpha];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
- (void)didSelectSendButton:(id)btn {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(faceViewClickSendMessageBtn)]) {
|
||||
[_delegate faceViewClickSendMessageBtn];
|
||||
}
|
||||
}
|
||||
- (void)didSelectDeleteButton:(id)btn {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(faceViewDidBackDelete:)]) {
|
||||
[_delegate faceViewDidBackDelete:(id)self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showDisplayView:(CGFloat)display_x
|
||||
display_y:(CGFloat)display_y
|
||||
targetView:(UIView *)targetView
|
||||
faceCell: (TUIFaceCell *)faceCell{
|
||||
|
||||
self.hasPreViewShow = YES;
|
||||
|
||||
UIViewController *contentViewController = [[UIViewController alloc] init];
|
||||
[contentViewController.view addSubview:self.dispalyView];
|
||||
self.dispalyImage.image = (faceCell.gifImage?:faceCell.face.image);
|
||||
CGFloat dispalyImagex = 5;
|
||||
CGFloat dispalyImagey = 5;
|
||||
CGFloat dispalyImagew = MIN(faceCell.face.image.size.width, 150) ;
|
||||
CGFloat dispalyImagewh = MIN(faceCell.face.image.size.height, 150);
|
||||
|
||||
self.dispalyView.frame = CGRectMake(display_x, display_y, dispalyImagew + 10, dispalyImagewh + 10);
|
||||
_dispalyImage.frame = CGRectMake(dispalyImagex, dispalyImagey, dispalyImagewh, dispalyImagewh);
|
||||
|
||||
contentViewController.view.backgroundColor = [UIColor clearColor
|
||||
];
|
||||
contentViewController.preferredContentSize = CGSizeMake(self.dispalyView.frame.size.width, self.dispalyView.frame.size.height);
|
||||
contentViewController.modalPresentationStyle = UIModalPresentationPopover;
|
||||
|
||||
UIPopoverPresentationController *popoverController = contentViewController.popoverPresentationController;
|
||||
|
||||
popoverController.sourceView = self;
|
||||
popoverController.sourceRect = CGRectMake(0, -10, self.frame.size.width, 0);
|
||||
popoverController.permittedArrowDirections = UIPopoverArrowDirectionDown;
|
||||
popoverController.delegate = self;
|
||||
popoverController.canOverlapSourceViewRect = NO;
|
||||
[self.mm_viewController presentViewController:contentViewController animated:YES completion:nil];
|
||||
popoverController.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
}
|
||||
- (UIImageView *)dispalyView {
|
||||
if (!_dispalyView) {
|
||||
_dispalyView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
|
||||
_dispalyView.contentMode = UIViewContentModeScaleToFill;
|
||||
_dispalyView.backgroundColor = [UIColor whiteColor];
|
||||
[_dispalyView addSubview:self.dispalyImage];
|
||||
}
|
||||
return _dispalyView;
|
||||
|
||||
}
|
||||
- (UIImageView *)dispalyImage {
|
||||
if (!_dispalyImage) {
|
||||
_dispalyImage = [[UIImageView alloc] init];
|
||||
}
|
||||
return _dispalyImage;
|
||||
}
|
||||
#pragma mark - UIPopoverPresentationControllerDelegate
|
||||
|
||||
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController {
|
||||
self.hasPreViewShow = NO;
|
||||
}
|
||||
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
|
||||
return UIModalPresentationNone;
|
||||
}
|
||||
@end
|
||||
135
TUIKit/TUIChat/BaseCell/TUIFaceView.h
Normal file
135
TUIKit/TUIChat/BaseCell/TUIFaceView.h
Normal file
@@ -0,0 +1,135 @@
|
||||
|
||||
// Created by Tencent on 2023/06/09.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
/**
|
||||
*
|
||||
* 【Module Description】
|
||||
* - This file declares the TUIFaceViewDelegate protocol and two classes, TUIFaceGroup and TUIFaceView.
|
||||
* - This file is used to implement the emoticon browsing view in the chat window, that is, the interface opened by clicking the smiley face icon in the
|
||||
* default state.
|
||||
* - Through this view, you can view and use all your emoticons, browse between different emoji groups, and further select and send emoticon message.
|
||||
* - This view has integrated the editing function of string-type expressions (such as [Smile]).
|
||||
*
|
||||
* 【Function description】
|
||||
* - TUIFaceView: Emoji view, which displays the emoticons of each group, and provides functions for selecting and deleting emoticons.
|
||||
* - TUIFaceGroup: Emoticons group. Including the initialization of the emoticons group, the positioning of a single emoticon, etc.
|
||||
*/
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TUIFaceView;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIFaceViewDelegate
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol TUIFaceViewDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* The callback after sliding to the specified emoticons group.
|
||||
* - You can use this callback to respond to the swipe operation, and then update the information of the emoticon view to display the emoticons in the new
|
||||
* emoticon group.
|
||||
*
|
||||
* @param faceView Delegator, emoticon view. Usually, the expression view has one and only one.
|
||||
* @param index The index of the emoji group to which slide.
|
||||
*/
|
||||
- (void)faceView:(TUIFaceView *)faceView scrollToFaceGroupIndex:(NSInteger)index;
|
||||
|
||||
|
||||
/**
|
||||
* The Callback after selecting a specific emoticon (index positioning).
|
||||
* You can use this callback to achieve:
|
||||
* - When a string type emoticon (such as [smile]) is clicked, add the emoticon to the input bar.
|
||||
* - When clicking on another type of emoji, send that emoji directly.
|
||||
*
|
||||
* @param faceView Delegator, emoticon view. Usually, the expression view has one and only one.
|
||||
* @param indexPath Index path, used to locate expressions. index.section: the group where the expression is located; index.row: the row where the expression
|
||||
* is located.
|
||||
*/
|
||||
- (void)faceView:(TUIFaceView *)faceView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* The action callback after clicking the delete button in the emoji view.
|
||||
* You can use this callback to delete the entire emoji string in the inputBar, for example, for "[smile]", delete the square brackets and the content between
|
||||
* the brackets directly, instead of only deleting the rightmost "]".
|
||||
*
|
||||
* @param faceView Delegator, emoticon view. Usually, the expression view has one and only one.
|
||||
*/
|
||||
- (void)faceViewDidBackDelete:(TUIFaceView *)faceView;
|
||||
@end
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TUIFaceView
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 【Module name】TUIFaceView
|
||||
* 【Function description】It is used to realize the emoticon browsing view in the chat window, that is, the interface opened by clicking the smiley face icon
|
||||
* in the default state.
|
||||
* - Through this view, you can view all available emoticons, support emoticon grouping, select and send emoticons.
|
||||
* - This view has integrated the editing functions of string-type emoticons (such as [smile]), and the selection and sending functions of image-type
|
||||
* emoticons.
|
||||
*/
|
||||
@interface TUIFaceView : UIView
|
||||
|
||||
/**
|
||||
* Line view
|
||||
* The separtor which distinguish emoticons from other views
|
||||
*/
|
||||
@property(nonatomic, strong) UIView *lineView;
|
||||
|
||||
/**
|
||||
* The collectionView of emoticon view
|
||||
* Contains multiple lines of expressions, and cooperates with faceFlowLayout for flexible and unified view layout.
|
||||
*/
|
||||
@property(nonatomic, strong) UICollectionView *faceCollectionView;
|
||||
|
||||
/**
|
||||
* The flow layout of @faceCollectionView
|
||||
* Cooperating with faceCollectionView to make the expression view more beautiful. Supported setting layout direction, line spacing, cell spacing, etc.
|
||||
*/
|
||||
@property(nonatomic, strong) UICollectionViewFlowLayout *faceFlowLayout;
|
||||
|
||||
/**
|
||||
* Page control
|
||||
* It is used to realize multi-page browsing of emoticons, can slide to switch emoticon pages, and display the total number of pages and the current number of
|
||||
* pages in the form of small dots below the emoticon page.
|
||||
*/
|
||||
@property(nonatomic, strong) UIPageControl *pageControl;
|
||||
|
||||
/**
|
||||
* The data of @faceView
|
||||
* The object stored in this NSMutableArray is TUIFaceGroup, that is, the expression group.
|
||||
*/
|
||||
@property(nonatomic, strong, readonly) NSMutableArray *faceGroups;
|
||||
@property(nonatomic, strong, readonly) NSMutableArray *sectionIndexInGroup;
|
||||
@property(nonatomic, strong, readonly) NSMutableArray *pageCountInGroup;
|
||||
@property(nonatomic, strong, readonly) NSMutableArray *groupIndexInSection;
|
||||
@property(nonatomic, strong, readonly) NSMutableDictionary *itemIndexs;
|
||||
|
||||
/**
|
||||
* Delegate variable, delegated
|
||||
* Need to implement the functionality required in the @TUIFaceViewDelegate protocol.
|
||||
*/
|
||||
@property(nonatomic, weak) id<TUIFaceViewDelegate> delegate;
|
||||
|
||||
/**
|
||||
* Swipe to the specified expression group.
|
||||
* Switch the emoticon group according to the subscript of the emoticon group clicked by the user.
|
||||
*
|
||||
* @param index The index of the destination group, starting from 0.
|
||||
*/
|
||||
- (void)scrollToFaceGroupIndex:(NSInteger)index;
|
||||
|
||||
/**
|
||||
* Setting data
|
||||
* Used to initialize TUIFaceView or update data in faceView when needed.
|
||||
*
|
||||
* @param data The data that needs to be set (TUIFaceGroup). The object stored in this NSMutableArray is TUIFaceGroup, that is, the emoticon group.
|
||||
*/
|
||||
- (void)setData:(NSMutableArray *)data;
|
||||
@end
|
||||
217
TUIKit/TUIChat/BaseCell/TUIFaceView.m
Normal file
217
TUIKit/TUIChat/BaseCell/TUIFaceView.m
Normal file
@@ -0,0 +1,217 @@
|
||||
//
|
||||
// TUIFaceView.m
|
||||
// UIKit
|
||||
//
|
||||
// Created by kennethmiao on 2018/9/18.
|
||||
// Copyright © 2018 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIFaceView.h"
|
||||
#import <TIMCommon/TIMCommonModel.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
|
||||
@interface TUIFaceView () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||
@property(nonatomic, strong) NSMutableArray *sectionIndexInGroup;
|
||||
@property(nonatomic, strong) NSMutableArray *pageCountInGroup;
|
||||
@property(nonatomic, strong) NSMutableArray *groupIndexInSection;
|
||||
@property(nonatomic, strong) NSMutableDictionary *itemIndexs;
|
||||
@property(nonatomic, assign) NSInteger sectionCount;
|
||||
@property(nonatomic, assign) NSInteger curGroupIndex;
|
||||
@end
|
||||
|
||||
@implementation TUIFaceView
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setupViews];
|
||||
[self defaultLayout];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = TUIChatDynamicColor(@"chat_input_controller_bg_color", @"#EBF0F6");
|
||||
|
||||
_faceFlowLayout = [[TUICollectionRTLFitFlowLayout alloc] init];
|
||||
_faceFlowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
_faceFlowLayout.minimumLineSpacing = TFaceView_Margin;
|
||||
_faceFlowLayout.minimumInteritemSpacing = TFaceView_Margin;
|
||||
_faceFlowLayout.sectionInset = UIEdgeInsetsMake(0, TFaceView_Page_Padding, 0, TFaceView_Page_Padding);
|
||||
|
||||
_faceCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_faceFlowLayout];
|
||||
[_faceCollectionView registerClass:[TUIFaceCell class] forCellWithReuseIdentifier:TFaceCell_ReuseId];
|
||||
_faceCollectionView.collectionViewLayout = _faceFlowLayout;
|
||||
_faceCollectionView.pagingEnabled = YES;
|
||||
_faceCollectionView.delegate = self;
|
||||
_faceCollectionView.dataSource = self;
|
||||
_faceCollectionView.showsHorizontalScrollIndicator = NO;
|
||||
_faceCollectionView.showsVerticalScrollIndicator = NO;
|
||||
_faceCollectionView.backgroundColor = self.backgroundColor;
|
||||
_faceCollectionView.alwaysBounceHorizontal = YES;
|
||||
[self addSubview:_faceCollectionView];
|
||||
|
||||
_lineView = [[UIView alloc] init];
|
||||
_lineView.backgroundColor = TIMCommonDynamicColor(@"separator_color", @"#DBDBDB");
|
||||
|
||||
_pageControl = [[UIPageControl alloc] init];
|
||||
_pageControl.currentPageIndicatorTintColor = TUIChatDynamicColor(@"chat_face_page_control_current_color", @"#7D7D7D");
|
||||
_pageControl.pageIndicatorTintColor = TUIChatDynamicColor(@"chat_face_page_control_color", @"#DEDEDE");
|
||||
_pageControl.userInteractionEnabled = NO;
|
||||
[self addSubview:_pageControl];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self defaultLayout];
|
||||
}
|
||||
|
||||
- (void)defaultLayout {
|
||||
_lineView.frame = CGRectMake(0, 0, self.frame.size.width, TLine_Heigh);
|
||||
_pageControl.frame = CGRectMake(0, self.frame.size.height - TFaceView_Page_Height, self.frame.size.width, TFaceView_Page_Height);
|
||||
_faceCollectionView.frame = CGRectMake(0, _lineView.frame.origin.y + _lineView.frame.size.height + TFaceView_Margin, self.frame.size.width,
|
||||
self.frame.size.height - _pageControl.frame.size.height - _lineView.frame.size.height - 2 * TFaceView_Margin);
|
||||
}
|
||||
|
||||
- (void)setData:(NSMutableArray *)data {
|
||||
_faceGroups = data;
|
||||
[self defaultLayout];
|
||||
|
||||
_sectionIndexInGroup = [NSMutableArray array];
|
||||
_groupIndexInSection = [NSMutableArray array];
|
||||
_itemIndexs = [NSMutableDictionary dictionary];
|
||||
_pageCountInGroup = [NSMutableArray array];
|
||||
|
||||
NSInteger sectionIndex = 0;
|
||||
for (NSInteger groupIndex = 0; groupIndex < _faceGroups.count; ++groupIndex) {
|
||||
TUIFaceGroup *group = _faceGroups[groupIndex];
|
||||
[_sectionIndexInGroup addObject:@(sectionIndex)];
|
||||
int itemCount = group.rowCount * group.itemCountPerRow;
|
||||
int sectionCount = ceil(group.faces.count * 1.0 / (itemCount - (group.needBackDelete ? 1 : 0)));
|
||||
[_pageCountInGroup addObject:@(sectionCount)];
|
||||
for (int sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex) {
|
||||
[_groupIndexInSection addObject:@(groupIndex)];
|
||||
}
|
||||
sectionIndex += sectionCount;
|
||||
}
|
||||
_sectionCount = sectionIndex;
|
||||
|
||||
for (NSInteger curSection = 0; curSection < _sectionCount; ++curSection) {
|
||||
NSNumber *groupIndex = _groupIndexInSection[curSection];
|
||||
NSNumber *groupSectionIndex = _sectionIndexInGroup[groupIndex.integerValue];
|
||||
TUIFaceGroup *face = _faceGroups[groupIndex.integerValue];
|
||||
NSInteger itemCount = face.rowCount * face.itemCountPerRow - face.needBackDelete;
|
||||
NSInteger groupSection = curSection - groupSectionIndex.integerValue;
|
||||
for (NSInteger itemIndex = 0; itemIndex < itemCount; ++itemIndex) {
|
||||
// transpose line/row
|
||||
NSInteger row = itemIndex % face.rowCount;
|
||||
NSInteger column = itemIndex / face.rowCount;
|
||||
NSInteger reIndex = face.itemCountPerRow * row + column + groupSection * itemCount;
|
||||
[_itemIndexs setObject:@(reIndex) forKey:[NSIndexPath indexPathForRow:itemIndex inSection:curSection]];
|
||||
}
|
||||
}
|
||||
|
||||
_curGroupIndex = 0;
|
||||
if (_pageCountInGroup.count != 0) {
|
||||
_pageControl.numberOfPages = [_pageCountInGroup[0] intValue];
|
||||
}
|
||||
[_faceCollectionView reloadData];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
|
||||
return _sectionCount;
|
||||
}
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
int groupIndex = [_groupIndexInSection[section] intValue];
|
||||
TUIFaceGroup *group = _faceGroups[groupIndex];
|
||||
return group.rowCount * group.itemCountPerRow;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TUIFaceCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:TFaceCell_ReuseId forIndexPath:indexPath];
|
||||
int groupIndex = [_groupIndexInSection[indexPath.section] intValue];
|
||||
TUIFaceGroup *group = _faceGroups[groupIndex];
|
||||
int itemCount = group.rowCount * group.itemCountPerRow;
|
||||
if (indexPath.row == itemCount - 1 && group.needBackDelete) {
|
||||
TUIFaceCellData *data = [[TUIFaceCellData alloc] init];
|
||||
data.path = TUIChatFaceImagePath(@"del_normal");
|
||||
[cell setData:data];
|
||||
cell.face.image = [cell.face.image rtl_imageFlippedForRightToLeftLayoutDirection];
|
||||
} else {
|
||||
NSNumber *index = [_itemIndexs objectForKey:indexPath];
|
||||
if (index.integerValue < group.faces.count) {
|
||||
TUIFaceCellData *data = group.faces[index.integerValue];
|
||||
[cell setData:data];
|
||||
} else {
|
||||
[cell setData:nil];
|
||||
}
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
int groupIndex = [_groupIndexInSection[indexPath.section] intValue];
|
||||
TUIFaceGroup *faces = _faceGroups[groupIndex];
|
||||
int itemCount = faces.rowCount * faces.itemCountPerRow;
|
||||
if (indexPath.row == itemCount - 1 && faces.needBackDelete) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(faceViewDidBackDelete:)]) {
|
||||
[_delegate faceViewDidBackDelete:self];
|
||||
}
|
||||
} else {
|
||||
NSNumber *index = [_itemIndexs objectForKey:indexPath];
|
||||
if (index.integerValue < faces.faces.count) {
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(faceView:didSelectItemAtIndexPath:)]) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index.integerValue inSection:groupIndex];
|
||||
[_delegate faceView:self didSelectItemAtIndexPath:indexPath];
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView
|
||||
layout:(UICollectionViewLayout *)collectionViewLayout
|
||||
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
int groupIndex = [_groupIndexInSection[indexPath.section] intValue];
|
||||
TUIFaceGroup *group = _faceGroups[groupIndex];
|
||||
CGFloat width = (self.frame.size.width - TFaceView_Page_Padding * 2 - TFaceView_Margin * (group.itemCountPerRow - 1)) / group.itemCountPerRow;
|
||||
CGFloat height = (collectionView.frame.size.height - TFaceView_Margin * (group.rowCount - 1)) / group.rowCount;
|
||||
return CGSizeMake(width, height);
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
NSInteger curSection = round(scrollView.contentOffset.x / scrollView.frame.size.width);
|
||||
if (curSection >= _groupIndexInSection.count) {
|
||||
return;
|
||||
}
|
||||
NSNumber *groupIndex = _groupIndexInSection[curSection];
|
||||
NSNumber *startSection = _sectionIndexInGroup[groupIndex.integerValue];
|
||||
NSNumber *pageCount = _pageCountInGroup[groupIndex.integerValue];
|
||||
if (_curGroupIndex != groupIndex.integerValue) {
|
||||
_curGroupIndex = groupIndex.integerValue;
|
||||
_pageControl.numberOfPages = pageCount.integerValue;
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(faceView:scrollToFaceGroupIndex:)]) {
|
||||
[_delegate faceView:self scrollToFaceGroupIndex:_curGroupIndex];
|
||||
}
|
||||
}
|
||||
_pageControl.currentPage = curSection - startSection.integerValue;
|
||||
}
|
||||
|
||||
- (void)scrollToFaceGroupIndex:(NSInteger)index {
|
||||
if (index > _sectionIndexInGroup.count) {
|
||||
return;
|
||||
}
|
||||
NSNumber *start = _sectionIndexInGroup[index];
|
||||
NSNumber *count = _pageCountInGroup[index];
|
||||
NSInteger curSection = ceil(_faceCollectionView.contentOffset.x / _faceCollectionView.frame.size.width);
|
||||
if (curSection > start.integerValue && curSection < start.integerValue + count.integerValue) {
|
||||
return;
|
||||
}
|
||||
CGRect rect =
|
||||
CGRectMake(start.integerValue * _faceCollectionView.frame.size.width, 0, _faceCollectionView.frame.size.width, _faceCollectionView.frame.size.height);
|
||||
[_faceCollectionView scrollRectToVisible:rect animated:NO];
|
||||
[self scrollViewDidScroll:_faceCollectionView];
|
||||
}
|
||||
@end
|
||||
24
TUIKit/TUIChat/BaseCell/TUIGroupNoticeCell.h
Normal file
24
TUIKit/TUIChat/BaseCell/TUIGroupNoticeCell.h
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// TUIGroupNoticeCell.h
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by harvy on 2022/1/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TUIGroupNoticeCellData.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TUIGroupNoticeCell : UITableViewCell
|
||||
|
||||
@property(nonatomic, strong) UILabel *nameLabel;
|
||||
@property(nonatomic, strong) UILabel *descLabel;
|
||||
@property(nonatomic, strong) UIImageView *iconView;
|
||||
|
||||
@property(nonatomic, strong) TUIGroupNoticeCellData *cellData;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
109
TUIKit/TUIChat/BaseCell/TUIGroupNoticeCell.m
Normal file
109
TUIKit/TUIChat/BaseCell/TUIGroupNoticeCell.m
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// TUIGroupNoticeCell.m
|
||||
// TUIGroup
|
||||
//
|
||||
// Created by harvy on 2022/1/11.
|
||||
// Copyright © 2023 Tencent. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TUIGroupNoticeCell.h"
|
||||
#import <TUICore/TUIThemeManager.h>
|
||||
#import <TIMCommon/TIMDefine.h>
|
||||
|
||||
@implementation TUIGroupNoticeCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
|
||||
[self setupViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupViews {
|
||||
self.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
self.contentView.backgroundColor = TIMCommonDynamicColor(@"form_bg_color", @"#FFFFFF");
|
||||
self.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
[self.contentView addSubview:self.nameLabel];
|
||||
[self.contentView addSubview:self.descLabel];
|
||||
|
||||
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
|
||||
tapRecognizer.delegate = self;
|
||||
tapRecognizer.cancelsTouchesInView = NO;
|
||||
[self.contentView addGestureRecognizer:tapRecognizer];
|
||||
}
|
||||
|
||||
- (void)tapGesture:(UIGestureRecognizer *)gesture {
|
||||
if (self.cellData.selector && self.cellData.target) {
|
||||
if ([self.cellData.target respondsToSelector:self.cellData.selector]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[self.cellData.target performSelector:self.cellData.selector];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCellData:(TUIGroupNoticeCellData *)cellData {
|
||||
_cellData = cellData;
|
||||
|
||||
self.nameLabel.text = cellData.name;
|
||||
self.descLabel.text = cellData.desc;
|
||||
// tell constraints they need updating
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
// update constraints now so we can animate the change
|
||||
[self updateConstraintsIfNeeded];
|
||||
|
||||
[self layoutIfNeeded];
|
||||
}
|
||||
|
||||
+ (BOOL)requiresConstraintBasedLayout {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// this is Apple's recommended place for adding/updating constraints
|
||||
- (void)updateConstraints {
|
||||
|
||||
[super updateConstraints];
|
||||
|
||||
[self.nameLabel sizeToFit];
|
||||
[self.nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(20);
|
||||
make.top.mas_equalTo(12);
|
||||
make.trailing.mas_lessThanOrEqualTo(self.contentView).mas_offset(-20);
|
||||
make.size.mas_equalTo(self.nameLabel.frame.size);
|
||||
}];
|
||||
[self.descLabel sizeToFit];
|
||||
[self.descLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.leading.mas_equalTo(self.nameLabel);
|
||||
make.top.mas_equalTo(self.nameLabel.mas_bottom).mas_offset(4);
|
||||
make.trailing.mas_lessThanOrEqualTo(self.contentView).mas_offset(-30);
|
||||
make.size.mas_equalTo(self.descLabel.frame.size);
|
||||
}];
|
||||
}
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (UILabel *)nameLabel {
|
||||
if (_nameLabel == nil) {
|
||||
_nameLabel = [[UILabel alloc] init];
|
||||
_nameLabel.text = @"";
|
||||
_nameLabel.textColor = TIMCommonDynamicColor(@"form_key_text_color", @"#888888");
|
||||
_nameLabel.font = [UIFont systemFontOfSize:16.0];
|
||||
}
|
||||
return _nameLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)descLabel {
|
||||
if (_descLabel == nil) {
|
||||
_descLabel = [[UILabel alloc] init];
|
||||
_descLabel.text = @"neirong";
|
||||
_descLabel.textColor = TIMCommonDynamicColor(@"form_subtitle_color", @"#BBBBBB");
|
||||
_descLabel.font = [UIFont systemFontOfSize:12.0];
|
||||
}
|
||||
return _descLabel;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user