tencent cloud

即时通信 IM

动态与公告
产品动态
公告
产品简介
产品概述
基本概念
应用场景
功能介绍
账号系统
用户资料与关系链
消息管理
群组相关
公众号系统
音视频通话 Call
使用限制
购买指南
计费概述
价格说明
购买指引
续费指引
停服说明
退费说明
开发指引
Demo 专区
开通服务
体验 Demo
快速跑通
下载中心
SDK & Demo 源码
更新日志
聊天互动(含 UI)
TUIKit 组件介绍
快速开始
全功能接入
单功能接入
AI 集成
构建基础界面
更多特性
定义外观
国际化界面语言
推送服务(Push)
服务概述
名词解释
开通服务
快速跑通
厂商通道
数据统计
排查工具
客户端 API
服务端 API
推送回调
高级功能
更新日志
错误码
常见问题
智能客服
功能概述
快速入门
集成指引
管理员操作手册
客服操作手册
更多实践
直播间搭建
AI 聊天机器人方案
超大娱乐协作社群
Discord 实现指南
游戏内集成 Chat 指南
类 WhatsApp Channel 搭建方案
发送红包
Chat 应对防火墙限制相关
无 UI 集成
快速开始
集成 SDK
初始化
登录登出
消息相关
会话相关
群组相关
社群话题
用户管理
离线推送
云端搜索
本地搜索
公众号
客户端 API
JavaScript
Android
iOS & macOS
Swift
Flutter
Electron
Unity
React Native
C 接口
C++
服务端 API
生成 UserSig
REST API
第三方回调
控制台指南
新版控制台介绍
创建并升级应用
基本配置
功能配置
账号管理
群组管理
公众号管理
回调配置
用量统计
资源包查看指南
实时监控
开发辅助工具
访问管理
高级功能
常见问题
uni-app 常见问题
购买相关问题
SDK 相关问题
账号鉴权相关问题
用户资料与关系链相关问题
消息相关问题
群组相关问题
直播群相关问题
昵称头像相关问题
协议与认证
服务等级协议
安全合规认证
IM 政策
隐私政策
数据隐私和安全协议
平滑迁移方案
平滑迁移完整版
平滑迁移简化版
错误码
联系我们

iOS

PDF
聚焦模式
字号
最后更新时间: 2025-05-29 10:22:07

概述

TUIChat 表情面板内置了部分 emoji 小表情,您也可以按需添加自定义表情。本文重点讲解添加自定义表情。
内置小表情面板
自定义表情面板


整个表情面板由两部分组成,如下图:
表情资源图片管理,包括:表情图片展示;
表情组管理,包括:表情组封面图,发送按钮。


新增自定义表情包

新增一套自定义表情包,您只需要按照如下两个步骤配置即可:
1. 准备表情资源
2. 启动 App 时加载表情包
需要说明的是,TUIChat 已经内置了表情包的发送和解析逻辑,您可以很轻松地实现自定义表情包的多端互通。
接下来以“programer” 这套自定义表情为例,演示如何添加自定义表情包,如下图。


准备表情资源

在添加表情包之前,您首先需要准备一套拥有版权的表情资源。如下图,只需要将您的表情图片打包成 bundle 文件即可。


加载表情包

如下图,将含有 “programer” 表情资源的自定义表情包 CustomFaceResource.bundle 拖到您的 xcode 工程中。然后在 App 启动时加载即可。

Swift
Objective-C
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// ...
self.setupCustomSticker()
return YES
}

func setupCustomSticker() {
guard let service = TIMCommonMediator.shared.getObject(for: TUIEmojiMeditorProtocol.self) else {
assertionFailure("There's not any object implement TUIEmojiMeditorProtocol")
return
}
let bundlePath = TUISwift.tuiBundlePath("CustomFaceResource", key: "TIMAppKit.TUIKit")

// 4350 group
var faces4350 = [TUIFaceCellData]()
for i in 0...17 {
let data = TUIFaceCellData()
let name = String(format: "yz%02d", i)
let path = "4350/\\(name)"
data.name = name
data.path = bundlePath + "/" + path
faces4350.append(data)
}
if faces4350.count > 0 {
let group4350 = TUIFaceGroup()
group4350.groupIndex = 1
group4350.groupPath = bundlePath + "/4350/"
group4350.faces = faces4350
group4350.rowCount = 2
group4350.itemCountPerRow = 5
group4350.menuPath = bundlePath + "/4350/menu"
service.appendFaceGroup(group4350)
}

// 4351 group
var faces4351 = [TUIFaceCellData]()
for i in 0...15 {
let data = TUIFaceCellData()
let name = String(format: "ys%02d", i)
let path = "4351/\\(name)"
data.name = name
data.path = bundlePath + "/" + path
faces4351.append(data)
}
if faces4351.count > 0 {
let group4351 = TUIFaceGroup()
group4351.groupIndex = 2
group4351.groupPath = bundlePath + "/4351/"
group4351.faces = faces4351
group4351.rowCount = 2
group4351.itemCountPerRow = 5
group4351.menuPath = bundlePath + "/4351/menu"
service.appendFaceGroup(group4351)
}

// 4352 group
var faces4352 = [TUIFaceCellData]()
for i in 0...16 {
let data = TUIFaceCellData()
let name = String(format: "gcs%02d", i)
let path = "4352/\\(name)"
data.name = name
data.path = bundlePath + "/" + path
faces4352.append(data)
}
if faces4352.count > 0 {
let group4352 = TUIFaceGroup()
group4352.groupIndex = 3
group4352.groupPath = bundlePath + "/4352/"
group4352.faces = faces4352
group4352.rowCount = 2
group4352.itemCountPerRow = 5
group4352.menuPath = bundlePath + "/4352/menu"
service.appendFaceGroup(group4352)
}
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
app = self;
// Load the emoji resources when starting the app
[self setupCustomSticker];
return YES;
}

- (void)setupCustomSticker {
// 1. Get the path of the bundle file of the custom sticker.
NSString *customFaceBundlePath = [[NSBundle mainBundle] pathForResource:@"CustomFaceResource" ofType:@"bundle"];

// 2. Load the custom emoji group
// 2.1 Load the `programer` emoji resource images and parse them into `TUIFaceCellData`
NSMutableArray<TUIFaceCellData *> *faceItems = [NSMutableArray array];
for (int i = 0; i <= 17; i++) {
TUIFaceCellData *data = [[TUIFaceCellData alloc] init];
// The filename of the emoji resource images (the extension can be saved) for multi-terminal connection (which requires that filenames are consistent)
data.name = [NSString stringWithFormat:@"yz%02d", i];
// The path of the emoji resource images for local display
data.path = [customFaceBundlePath stringByAppendingPathComponent:[NSString stringWithFormat:@"programer/%@", data.name]];
[faceItems addObject:data];
}
// 2.2 Create the `programer` emoji group and parse it into `TUIFaceGroup`
TUIFaceGroup *programGroup = [[TUIFaceGroup alloc] init];
// Indicate the serial number of the current emoji group on the emoji panel for multi-terminal connection (which can be used together with the emoji name to find an image on the receiver's device)
// Note that `groupIndex` starts from `0` and indicates the actual position of the current sticker on the emoji panel (`0` is the default value for the built-in `emoji` emoji group)
programGroup.groupIndex = 1;
// The root path of the current sticker in the bundle file of the custom emojis
programGroup.groupPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/"];
// The emoji resources in the current sticker
programGroup.faces = faceItems;
// The layout of the current sticker
programGroup.rowCount = 2;
programGroup.itemCountPerRow = 5;
// The path of the thumbnail of the current sticker (without the extension)
programGroup.menuPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/menu"];

// 3. Add the `programer` emoji group to the emoji panel
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
[service appendFaceGroup:programGroup];
}

多端互通

TUIChat 已经内置了表情包发送和解析逻辑,您只需要将如下两个属性在各个平台保持一致即可:
表情包中的图片文件名一致,也即 App 启动加载表情包时解析成 TUIFaceCellDataname 字段值需要多端一致;
表情包在表情面板中的顺序一致,也即 App 启动加载表情包时解析成 TUIFaceGroupgroupIndex 字段值需要多端一致。
当上述两个信息一致后,TUIChat 内置的表情包发送逻辑会将表情文件名和所属的表情包索引信息发给其他端,从而实现多端互通。
需要注意的是,groupIndex 是从 0 开始的,标识了当前表情包在表情面板中的实际位置(内置的 emoji 表情组默认是 0)。


表情面板高级配置

调整表情面板顺序

TUIChat 的表情面板支持调整表情组的顺序,您只需要按照实际顺序调用 TUIConfig- appendFaceGroup: 方法即可。
如果您想将内置 emoji 表情组调整到自定义表情后面,需要按照如下方式操作:
获取当前表情面板内置的表情组 TUIConfig.defaultConfig.faceGroups
重新排序;
将已经排好序的表情组列表赋值给表情面板。
Swift
Objective-C
func setupCustomSticker() {
guard let service = TIMCommonMediator.shared.getObject(for: TUIEmojiMeditorProtocol.self) else {
assertionFailure("There's not any object implement TUIEmojiMeditorProtocol")
return
}
let bundlePath = TUISwift.tuiBundlePath("CustomFaceResource", key: "TIMAppKit.TUIKit")

// 4350 group
var faces4350 = [TUIFaceCellData]()
for i in 0...17 {
let data = TUIFaceCellData()
let name = String(format: "yz%02d", i)
let path = "4350/\\(name)"
data.name = name
data.path = bundlePath + "/" + path
faces4350.append(data)
}
if faces4350.count > 0 {
let group4350 = TUIFaceGroup()
group4350.groupIndex = 1
group4350.groupPath = bundlePath + "/4350/"
group4350.faces = faces4350
group4350.rowCount = 2
group4350.itemCountPerRow = 5
group4350.menuPath = bundlePath + "/4350/menu"
service.appendFaceGroup(group4350)
}

// 4351 group
var faces4351 = [TUIFaceCellData]()
for i in 0...15 {
let data = TUIFaceCellData()
let name = String(format: "ys%02d", i)
let path = "4351/\\(name)"
data.name = name
data.path = bundlePath + "/" + path
faces4351.append(data)
}
if faces4351.count > 0 {
let group4351 = TUIFaceGroup()
group4351.groupIndex = 2
group4351.groupPath = bundlePath + "/4351/"
group4351.faces = faces4351
group4351.rowCount = 2
group4351.itemCountPerRow = 5
group4351.menuPath = bundlePath + "/4351/menu"
service.appendFaceGroup(group4351)
}

// 4352 group
var faces4352 = [TUIFaceCellData]()
for i in 0...16 {
let data = TUIFaceCellData()
let name = String(format: "gcs%02d", i)
let path = "4352/\\(name)"
data.name = name
data.path = bundlePath + "/" + path
faces4352.append(data)
}
if faces4352.count > 0 {
let group4352 = TUIFaceGroup()
group4352.groupIndex = 3
group4352.groupPath = bundlePath + "/4352/"
group4352.faces = faces4352
group4352.rowCount = 2
group4352.itemCountPerRow = 5
group4352.menuPath = bundlePath + "/4352/menu"
service.appendFaceGroup(group4352)
}
}

- (void)setupCustomSticker {
// 1. Get the path of the bundle file of the custom sticker.
NSString *customFaceBundlePath = [[NSBundle mainBundle] pathForResource:@"CustomFaceResource" ofType:@"bundle"];
// 2. Load the custom emoji group
// 2.1 Load the `programer` emoji resource images and parse them into `TUIFaceCellData`
NSMutableArray<TUIFaceCellData *> *faceItems = [NSMutableArray array];
for (int i = 0; i <= 17; i++) {
TUIFaceCellData *data = [[TUIFaceCellData alloc] init];
// The filename of the emoji resource images (the extension can be saved) for multi-terminal connection (which requires that filenames are consistent)
data.name = [NSString stringWithFormat:@"yz%02d", i];
// The path of the emoji resource images for local display
data.path = [customFaceBundlePath stringByAppendingPathComponent:[NSString stringWithFormat:@"programer/%@", data.name]];
[faceItems addObject:data];
}
// 2.2 Create the `programer` emoji group and parse it into `TUIFaceGroup`
TUIFaceGroup *programGroup = [[TUIFaceGroup alloc] init];
// Indicate the serial number of the current emoji group on the emoji panel for multi-terminal connection (which can be used together with the emoji name to find an image on the receiver's device)
// Note that `groupIndex` starts from `0` and indicates the actual position of the current sticker on the emoji panel (`0` is the default value for the built-in `emoji` emoji group)
programGroup.groupIndex = 0;
// The root path of the current sticker in the bundle file of the custom emojis
programGroup.groupPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/"];
// The emoji resources in the current sticker
programGroup.faces = faceItems;
// The layout of the current sticker
programGroup.rowCount = 2;
programGroup.itemCountPerRow = 5;
// The path of the thumbnail of the current sticker (without the extension)
programGroup.menuPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/menu"];

// 3. Add the `programer` emoji group to the front of the emoji panel
id<TUIEmojiMeditorProtocol> service = [[TIMCommonMediator share] getObject:@protocol(TUIEmojiMeditorProtocol)];
[service appendFaceGroup:programGroup];
}
说明:
由于表情包多端互通依赖于表情图片的名称和表情组所在面板的顺序,当您调整本地顺序之后,需要保证 groupIndex 与您实际顺序一致,方便各端互通。

修改表情组封面

您可以在加载自定义表情组时,给 TUIFaceGroupmenuPath 属性设置封面图的路径(无需 @2x.png 的扩展名)来自定义表情组封面。
例如,将 "programer" 表情组中的 menu@2x.png 图片作为封面图片。
Swift
Objective-C
func setupCustomSticker() {
// ...
// 4350 group
var faces4350 = [TUIFaceCellData]()
for i in 0...17 {
let data = TUIFaceCellData()
let name = String(format: "yz%02d", i)
let path = "4350/\\(name)"
data.name = name
data.path = bundlePath + "/" + path
faces4350.append(data)
}
if faces4350.count > 0 {
let group4350 = TUIFaceGroup()
group4350.groupIndex = 1
group4350.groupPath = bundlePath + "/4350/"
group4350.faces = faces4350
group4350.rowCount = 2
group4350.itemCountPerRow = 5
group4350.menuPath = bundlePath + "/4350/menu"
service.appendFaceGroup(group4350)
}
// ...
}

- (void)setupCustomSticker {
....

// 2.2 Create the `programer` emoji group and parse it into `TUIFaceGroup`
TUIFaceGroup *programGroup = [[TUIFaceGroup alloc] init];
....
// The path of the thumbnail of the current sticker (without the extension)
programGroup.menuPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/menu"];
....

....
}

调整表情图片的布局

目前 TUIChat 表情面板针对表情图片的布局,支持以下两个样式:
rowCount,当前表情组内图片显示的行数;
itemCountPerRow,每行展示的表情图片的个数。
例如,调整 “programer” 表情组中的表情图片排列规则是每页 2 行,每行最多 5 张图片。
Swift
Objective-C
func setupCustomSticker() {
// ...
// 4350 group
var faces4350 = [TUIFaceCellData]()
for i in 0...17 {
let data = TUIFaceCellData()
let name = String(format: "yz%02d", i)
let path = "4350/\\(name)"
data.name = name
data.path = bundlePath + "/" + path
faces4350.append(data)
}
if faces4350.count > 0 {
let group4350 = TUIFaceGroup()
group4350.groupIndex = 1
group4350.groupPath = bundlePath + "/4350/"
group4350.faces = faces4350
group4350.rowCount = 2
group4350.itemCountPerRow = 5
group4350.menuPath = bundlePath + "/4350/menu"
service.appendFaceGroup(group4350)
}
// ...
}

- (void)setupCustomSticker {
...

// 2.2 Create the `programer` emoji group and parse it into `TUIFaceGroup`
TUIFaceGroup *programGroup = [[TUIFaceGroup alloc] init];
// The layout of the current sticker
programGroup.rowCount = 2;
programGroup.itemCountPerRow = 5;

...
}

表情包渲染原理

TUIChat 内置了表情包的发送和渲染机制,您无需关注本部分内容。
如果您想修改源码,或者需要将自定义表情内容编码后直接透传,可以参考该部分。

发送表情

TUIChat 的表情面板由 UICollectionView 组成,当点击每个表情图片后会触发 TUIInputController- faceView:didSelectItemAtIndexPath: 方法,并将您点选的表情名称和对应表情组在面板中的索引信息回调给您。
您可以在回调中通过两个步骤将表情发送出去:
使用表情名称和表情组索引创建表情消息;
调用 TUIChat 的方法将表情消息发送出去。
Swift
Objective-C
public func faceVerticalView(_ faceView: TUIFaceVerticalView, didSelectItemAtIndexPath indexPath: IndexPath) {
let group = faceView.faceGroups[indexPath.section]
if let face = group.faces?[indexPath.row] as? TUIFaceCellData {
if group.isNeedAddInInputBar {
inputBar?.addEmoji(face)
updateRecentMenuQueue(face.name ?? "")
} else {
let message = V2TIMManager.sharedInstance().createFaceMessage(index: Int32(group.groupIndex), data: face.name?.data(using: .utf8) ?? Data())!
delegate?.inputController(self, didSendMessage: message)
}
}
}
- (void)faceView:(TUIFaceView *)faceView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
TUIFaceGroup *group = [TUIConfig defaultConfig].faceGroups[indexPath.section];
TUIFaceCellData *face = group.faces[indexPath.row];
if(indexPath.section == 0){
// Built-in emojis need to be displayed in the input box.
[_inputBar addEmoji:face];
}
else{
// Custom emojis are directly sent to the receiver.
if (face.name) {
// Create an emoji message
V2TIMMessage *message = [[V2TIMManager sharedInstance] createFaceMessage:group.groupIndex data:[face.name dataUsingEncoding:NSUTF8StringEncoding]];
// Send the message to receiver
if(_delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]){
[_delegate inputController:self didSendMessage:message];
}
}
}
}

解析表情并渲染

当收到对端的表情消息后,TUIChat 会触发 TUIFaceMessageCellData- getCellData: 方法,并在其中将表情消息解析成用于展示表情的 TUIFaceMessageCellData
TUIChat 会将解析到的 TUIMessageCellData 赋值给 TUIFaceMessageCell 用于渲染。
关于整个 TUIChat 的消息解析流程可以参见 含 UI 集成方案 - 添加自定义消息
Swift
Objective-C
override class func getCellData(message: V2TIMMessage) -> TUIMessageCellData {
guard let elem = message.faceElem else { return TUIFaceMessageCellData(direction: .incoming) }
let faceData = TUIFaceMessageCellData(direction: message.isSelf ? .outgoing : .incoming)
faceData.groupIndex = elem.index
if let data = elem.data {
faceData.faceName = String(data: data, encoding: .utf8)
}

if let groups = TIMConfig.shared.faceGroups {
for group in groups {
if group.groupIndex == faceData.groupIndex {
if let url = URL(string: group.groupPath ?? "") {
let path = url.appendingPathComponent(faceData.faceName ?? "").path
faceData.path = path
}
break
}
}
}

faceData.reuseId = "TFaceMessageCell"
return faceData
}
+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message{
// Parse the emoji information after receiving the message
V2TIMFaceElem *elem = message.faceElem;

// Create the `TUIFaceMessageCellData` for emoji display
TUIFaceMessageCellData *faceData = [[TUIFaceMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
// Get the order information of the current emoji group on the emoji panel
faceData.groupIndex = elem.index;
// Get the filename of the emoji image
faceData.faceName = [[NSString alloc] initWithData:elem.data encoding:NSUTF8StringEncoding];
// Get the specific path of the local sticker of the emoji image based on the name of the emoji image and the emoji group
for (TUIFaceGroup *group in [TUIConfig defaultConfig].faceGroups) {
if(group.groupIndex == faceData.groupIndex){
NSString *path = [group.groupPath stringByAppendingPathComponent:faceData.faceName];
faceData.path = path;
break;
}
}
faceData.reuseId = TFaceMessageCell_ReuseId;
return faceData;
}


帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈