无 UI 集成方案

服务端 API

诚邀爱技术、爱分享的你,成为文档内容共建者> HOT

概述

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

内置小表情面板
自定义表情面板

整个表情面板由两部分组成,如下图:

  • 表情资源图片管理,包括:表情图片展示;
  • 表情组管理,包括:表情组封面图,发送按钮。

新增自定义表情包

新增一套自定义表情包,您只需要按照如下两个步骤配置即可:

  1. 准备表情资源
  2. 启动 App 时加载表情包

需要说明的是,TUIChat 已经内置了表情包的发送和解析逻辑,您可以很轻松地实现自定义表情包的多端互通。

接下来以“programer” 这套自定义表情为例,演示如何添加自定义表情包,如下图。

准备表情资源

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

加载表情包

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

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    app = self;
    // App 启动时,加载表情资源
    [self setupCustomSticker];    
    return YES;
}

- (void)setupCustomSticker {
    // 1 获取自定义表情包 bundle 的路径
    NSString *customFaceBundlePath = [[NSBundle mainBundle] pathForResource:@"CustomFaceResource" ofType:@"bundle"];

    // 2 加载自定义表情组
    // 2.1 加载 programer 表情资源图片,并解析成 TUIFaceCellData
    NSMutableArray<TUIFaceCellData *> *faceItems = [NSMutableArray array];
    for (int i = 0; i <= 17; i++) {
        TUIFaceCellData *data = [[TUIFaceCellData alloc] init];
        // 表情资源图片的文件名(可不带扩展名),用于多端互通(多端互通时,文件名需要保持一致)
        data.name = [NSString stringWithFormat:@"yz%02d", i];
        // 表情资源图片的路径,用于本地显示
        data.path = [customFaceBundlePath stringByAppendingPathComponent:[NSString stringWithFormat:@"programer/%@", data.name]];
        [faceItems addObject:data];
    }
    // 2.2 创建 programer 表情组,并解析成 TUIFaceGroup
    TUIFaceGroup *programGroup = [[TUIFaceGroup alloc] init];
    // 标识当前表情组在表情面板的序列号, 用于多端互通(多端互通时与表情的 name 一起在对端设备查找对应图片)
    // 需要注意的是,groupIndex 是从 0 开始的,标识了当前表情包在表情面板中的实际位置(内置的 emoji 表情组默认是 0)
    programGroup.groupIndex = 1;
    // 当前表情包在自定义表情bundle中的根路径
    programGroup.groupPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/"];
    // 当前表情包的表情资源
    programGroup.faces = faceItems;
    // 当前表情包的布局
    programGroup.rowCount = 2;
    programGroup.itemCountPerRow = 5;
    // 当前表情包的封面图的路径(不带扩展名)
    programGroup.menuPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/menu"];

    // 3 将 program 表情组添加到表情面板中
    [TUIConfig.defaultConfig appendFaceGroup:programGroup];
}

多端互通

TUIChat 已经内置了表情包发送和解析逻辑,您只需要将如下两个属性在各个平台保持一致即可:

  • 表情包中的图片文件名一致,也即 App 启动加载表情包时解析成 TUIFaceCellDataname 字段值需要多端一致;
  • 表情包在表情面板中的顺序一致,也即 App 启动加载表情包时解析成 TUIFaceGroupgroupIndex 字段值需要多端一致。

当上述两个信息一致后,TUIChat 内置的表情包发送逻辑会将表情文件名和所属的表情包索引信息发给其他端,从而实现多端互通。

需要注意的是,groupIndex 是从 0 开始的,标识了当前表情包在表情面板中的实际位置(内置的 emoji 表情组默认是 0)。

表情面板高级配置

调整表情面板顺序

TUIChat 的表情面板支持调整表情组的顺序,您只需要按照实际顺序调用 TUIConfig- appendFaceGroup: 方法即可。

如果您想将内置 emoji 表情组调整到自定义表情后面,需要按照如下方式操作:

  • 获取当前表情面板内置的表情组 TUIConfig.defaultConfig.faceGroups
  • 重新排序;
  • 将已经排好序的表情组列表赋值给表情面板。

- (void)setupCustomSticker {
    // 1 获取自定义表情包 bundle 的路径
    NSString *customFaceBundlePath = [[NSBundle mainBundle] pathForResource:@"CustomFaceResource" ofType:@"bundle"];

    // 2 加载自定义表情组
    // 2.1 加载 programer 表情资源图片,并解析成 TUIFaceCellData
    NSMutableArray<TUIFaceCellData *> *faceItems = [NSMutableArray array];
    for (int i = 0; i <= 17; i++) {
        TUIFaceCellData *data = [[TUIFaceCellData alloc] init];
        // 表情资源图片的文件名(可不带扩展名),用于多端互通(多端互通时,文件名需要保持一致)
        data.name = [NSString stringWithFormat:@"yz%02d", i];
        // 表情资源图片的路径,用于本地显示
        data.path = [customFaceBundlePath stringByAppendingPathComponent:[NSString stringWithFormat:@"programer/%@", data.name]];
        [faceItems addObject:data];
    }
    // 2.2 创建 programer 表情组,并解析成 TUIFaceGroup
    TUIFaceGroup *programGroup = [[TUIFaceGroup alloc] init];
    // 标识当前表情组在表情面板的序列号, 用于多端互通(多端互通时与表情的 name 一起在对端设备查找对应图片)
    // 需要注意的是,groupIndex 是从 0 开始的,标识了当前表情包在表情面板中的实际位置(内置的 emoji 表情组默认是 0)
    programGroup.groupIndex = 0;
    // 当前表情包在自定义表情bundle中的根路径
    programGroup.groupPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/"];
    // 当前表情包的表情资源
    programGroup.faces = faceItems;
    // 当前表情包的布局
    programGroup.rowCount = 2;
    programGroup.itemCountPerRow = 5;
    // 当前表情包的封面图的路径(不带扩展名)
    programGroup.menuPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/menu"];

    // 3 将 programer 表情组添加到表情面板最前面
    // 3.1 获取内置表情组
    NSMutableArray *faceGroupsMenu = [NSMutableArray arrayWithArray:TUIConfig.defaultConfig.faceGroups];
    // 3.2 将 programer 添加到表情组最前面
    [faceGroupsMenu insertObject:programGroup atIndex:0];
    // 3.3 重新赋值表情面板
    TUIConfig.defaultConfig.faceGroups = faceGroupsMenu;

    // 4 如果您不想改动内置表情的顺序,只需要按顺序添加即可。
    // [TUIConfig.defaultConfig appendFaceGroup:programGroup];

    // 需要注意的是,需要保证 groupIndex 与实际的顺序一致
    // 可以直接读取当前的位置复制
    programGroup.groupIndex = [TUIConfig.defaultConfig.faceGroups indexOfObject:programGroup];
}

说明:

由于表情包多端互通依赖于表情图片的名称和表情组所在面板的顺序,当您调整本地顺序之后,需要保证 groupIndex 与您实际顺序一致,方便各端互通。

修改表情组封面

您可以在加载自定义表情组时,给 TUIFaceGroupmenuPath 属性设置封面图的路径(无需 @2x.png 的扩展名)来自定义表情组封面。

例如,将 "programer" 表情组中的 menu@2x.png 图片作为封面图片。

- (void)setupCustomSticker {
    ....

    // 2.2 创建 programer 表情组,并解析成 TUIFaceGroup
    TUIFaceGroup *programGroup = [[TUIFaceGroup alloc] init];
    ....
    // 当前表情包的封面图的路径(不带扩展名)
    programGroup.menuPath = [customFaceBundlePath stringByAppendingPathComponent:@"programer/menu"];
    ....

    ....
}

调整表情图片的布局

目前 TUIChat 表情面板针对表情图片的布局,支持以下两个样式:

  • rowCount,当前表情组内图片显示的行数;
  • itemCountPerRow,每行展示的表情图片的个数。

例如,调整 “programer” 表情组中的表情图片排列规则是每页 2 行,每行最多 5 张图片。

- (void)setupCustomSticker {
    ...

    // 2.2 创建 programer 表情组,并解析成 TUIFaceGroup
    TUIFaceGroup *programGroup = [[TUIFaceGroup alloc] init];
    // 当前表情包的布局
    programGroup.rowCount = 2;
    programGroup.itemCountPerRow = 5;

    ...
}

表情包渲染原理

TUIChat 内置了表情包的发送和渲染机制,您无需关注本部分内容。

如果您想修改源码,或者需要将自定义表情内容编码后直接透传,可以参考该部分。

发送表情

TUIChat 的表情面板由 UICollectionView 组成,当点击每个表情图片后会触发 TUIInputController- faceView:didSelectItemAtIndexPath: 方法,并将您点选的表情名称和对应表情组在面板中的索引信息回调给您。

您可以在回调中通过两个步骤将表情发送出去:

  • 使用表情名称和表情组索引创建表情消息;
  • 调用 TUIChat 的方法将表情消息发送出去。

- (void)faceView:(TUIFaceView *)faceView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    TUIFaceGroup *group = [TUIConfig defaultConfig].faceGroups[indexPath.section];
    TUIFaceCellData *face = group.faces[indexPath.row];
    if(indexPath.section == 0){
        // 如果是内置的 emoji 小表情,需要将小表情展示在输入框中
        [_inputBar addEmoji:face];
    }
    else{
        // 如果是自定义表情,直接发送给对端
        if (face.name) {
            // 创建表情消息
            V2TIMMessage *message = [[V2TIMManager sharedInstance] createFaceMessage:group.groupIndex data:[face.name dataUsingEncoding:NSUTF8StringEncoding]];
            // 发送给对端
            if(_delegate && [_delegate respondsToSelector:@selector(inputController:didSendMessage:)]){
                [_delegate inputController:self didSendMessage:message];
            }
        }
    }
}

解析表情并渲染

当收到对端的表情消息后,TUIChat 会触发 TUIFaceMessageCellData- getCellData: 方法,并在其中将表情消息解析成用于展示表情的 TUIFaceMessageCellData

TUIChat 会将解析到的 TUIMessageCellData 赋值给 TUIFaceMessageCell 用于渲染。

关于整个 TUIChat 的消息解析流程可以参见 含 UI 集成方案 - 添加自定义消息

+ (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
    // 收到消息后,取出表情信息
    V2TIMFaceElem *elem = message.faceElem;

    // 创建用于显示表情的 TUIFaceMessageCellData
    TUIFaceMessageCellData *faceData = [[TUIFaceMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
    // 获取当前表情组在表情面板中的顺序
    faceData.groupIndex = elem.index;
    // 获取表情图片的文件名称
    faceData.faceName = [[NSString alloc] initWithData:elem.data encoding:NSUTF8StringEncoding];
    // 根据表情图片的名称和表情组,获取表情图片在本地表情包的具体路径
    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;
}

交流与反馈

欢迎加入 QQ 群进行技术交流和反馈问题。

目录