融云 Flutter IM SDK 解析

最近准备使用融云的 Flutter SDK,所以顺便记录一下。融云 Flutter IM SDK 地址:传送门融云的 Flutter SDK 是基于 融云 IMLib 层做的封装,封装了 IMLib 的部分接口提供给 Flutter 开发者使用。此文章只介绍了...
继续阅读 »

最近准备使用融云的 Flutter SDK,所以顺便记录一下。

融云 Flutter IM SDK 地址:传送门

融云的 Flutter SDK 是基于 融云 IMLib 层做的封装,封装了 IMLib 的部分接口提供给 Flutter 开发者使用。此文章只介绍了 Flutter 层做的一些操作。

目录结构

image.png

整体 SDK 的结构规规矩矩,核心内容参考红色箭头即可。

SDK 层包含 三个目录:
android:此目录包含了和原生 SDK 交互的所有 Java 文件
ios:此目录包含了和原生 SDK 交互的所有 oc 文件
lib: 此目录为使用 dart 编写的 Flutter SDK 文件

其他目录:
doc:主要是融云开发者提供的一些文档相关
example:是融云开发者基于此 SDK 提供的一个简单示例,整体较为简陋,且有细微 bug,仅供参考

FunctionList.md 是融云开发者提供的一个功能清单,
大体如下:

# RongCloud IM Flutter SDK 功能清单

## 连接

初始化

连接

断开连接

连接状态兼容

## 配置

设置服务器地址( im 服务;文件服务)

## 会话

获取会话列表,支持全量获取,分页获取

获取单个会话

删除指定会话



## 消息

当前仅支持 文本消息,语音消息,图片消息,小视频消息

收发消息(可以携带 pushContent)

自定义消息

获取批量本地历史消息

获取单条本地历史消息

获取批量远端历史消息

插入消息

删除批量本地消息

获取未读数

清除指定会话未读数

## 免打扰

设置会话免打扰

获取会话免打扰

## 会话置顶

设置会话置顶

备注:获取会话是可以获取到会话置顶状态

## 黑名单

加入黑名单

移除黑名单

获取黑名单列表

检查特定用户的黑名单状态

## 聊天室

加入聊天室

退出聊天室

获取聊天室信息

都是一些接口层的操作。

剩下的文件基本可以忽略。

解析

我们开始从 lib 目录开始分析

image.png

这两个文件是核心文件,我们所有的调用接口都在这里。

rong_im_client.dart 是最新版本的接口类,我们只关心这个即可。

rong_im_plugin.dart 是旧版本的接口类,已经废弃。

common_define.dart 是定义了 SDK 内使用的所有枚举和状态码。

method_key.dart是定义了 Flutter SDK 和原生层进行交互时标识的唯一的常量字符串。

然后就是剩下的三个文件目录:
image.png

info: 其实就是对象 model 类

image.png

message: 这个是 Flutter SDK提供的所有内置的消息类型,包括聊天室KV消息、合并消息、文件消息、GIF消息、图片消息、位置消息、撤回消息、引用消息、图文消息、视频消息、文本消息、语音消息。

其中有一个比较特殊的 dart 类就是 message_content.dart, 这个类是所有消息的基类。

每个消息都需要继承此类,并复写里面的方法进行编解码。

image.png

util: 这个目录就是一个工具目录。

message_factory.dart 主要是进行消息封装,字典转模型,模型转字典,根本原因我猜就是 Flutter 和 原生仅有的交互类型限制。

type_util.dart:略

以上就是 SDK 在 Flutter 层做的所有的操作,内容整体不多,而且所有代码都是开源的,看看基本上都了解。

与原生层的交互

image.png

整体都是通过 invokeMethod 与原生进行通信的,后面的key 就是之前说的常量字符串,用来保证唯一性。

而在 iOS 和 Android 都提供了对应的定义。

image.png

image.png

并通过触发 iOS 和 Android 层对应的方法来进行处理。

image.png

image.png

原生层

Android:
image.png

整体核心的处理都在 RCIMFlutterWrapper.java 这个类处理了 dart 代码传递过来的数据,并与融云 IMLib 的 SDK 进行了处理。

iOS:
image.png

同样,iOS 的核心代码也都在 RCIMFlutterWrapper.m 这个类处理。具体内容大家可以自己看一下。

到此整个内容我们也就大体明白了。

整体看来其实就是 flutter 中 rong_im_client.dart 和 原生的两个 Wrapper 类做交互,其他的都是助攻。

整体看来不难,搞起~

融云文档:传送门


收起阅读 »

融云自定义消息不显示

项目用的融云,IMKit SDK(自带 UI),但是出现一个问题,就是自定义消息在会话页面刚收到的时候能显示,但是退出会话页面再进入就不显示了。非常的纳闷啊。查询了存储策略,编解码方法,都没有问题。后来提交工单,技术人员给了反馈才发现自己把消息的注册放到了初始...
继续阅读 »

项目用的融云,IMKit SDK(自带 UI),但是出现一个问题,就是自定义消息在会话页面刚收到的时候能显示,但是退出会话页面再进入就不显示了。非常的纳闷啊。查询了存储策略,编解码方法,都没有问题。后来提交工单,技术人员给了反馈才发现自己把消息的注册放到了初始化 appkey 前边,然后人家融云写的很明确:使用融云SDK所有功能(包括显示SDK中或者继承于SDK的View)之前,您必须先调用此方法初始化 SDK。可见认真查看文档接口注释的重要性!!

/*!
 初始化融云SDK

 @param appKey  从融云开发者平台创建应用后获取到的App Key

 @discussion 您在使用融云SDK所有功能(包括显示SDK中或者继承于SDK的View)之前,您必须先调用此方法初始化SDK。
 在App整个生命周期中,您只需要执行一次初始化。

 @warning 如果您使用IMKit,请使用此方法初始化SDK;
 如果您使用IMLib,请使用RCIMClient中的同名方法初始化,而不要使用此方法。
 */
- (void)initWithAppKey:(NSString *)appKey;

融云(www.rongcloud.cn)

收起阅读 »

如何利用融云 IMLib 来实现一个阅后即焚功能

场景项目需要在私聊中来实现一个阅后即焚的功能,即 A 用户给 B 用户发送消息,B 用户在进入聊天页面查看之后 A 用户删除此消息,B 用户开始进入倒计时,倒计时结束后,删除此消息。思考大体的梳理一下具体的逻辑A -> BB 进入会话页面B 将此消息开始...
继续阅读 »

场景

项目需要在私聊中来实现一个阅后即焚的功能,即 A 用户给 B 用户发送消息,B 用户在进入聊天页面查看之后 A 用户删除此消息,B 用户开始进入倒计时,倒计时结束后,删除此消息。

思考

  1. 大体的梳理一下具体的逻辑

    • A -> B

    • B 进入会话页面

    • B 将此消息开始倒计时

    • 通知 A 我已进行阅读

    • A 删除消息

  2. 从上面内容我们来大体的设计一下我们需要用户的技术

    • 单例类

    • 自定义消息,用来告诉 A 我已经开始阅读了,你删除吧

    • 一个用于维护阅后即焚消息的管理类

    • 一个存储 A 给 B 发送的所有的阅后即焚的消息的容器 A k 为 targetid ,v 为 messageIDs

    • 一个存储每条阅后即焚消息的容器 B k 为 messageId, v 为当前消息还剩的倒计时时间。

    • 一个用来存储所有阅后即焚消息的容器 C K:ID V:msg

    • 两个处理队列 一个处理时间 一个处理消息

  3. 对外暴露接口

    • 代理 接收方焚烧消息的每秒倒计时

    • 通知 接收方收到对方已阅读某条消息的通知

详解

  1. 初始化我们的所有容器

  2. 收到消息,在合适的业务时机将此消息加入到焚烧队列

  3. 查询消息是否已经在焚烧队列

  4. 如果不在,添加到 A B C容器

  5. 执行倒计时

倒计时操作

  1. 遍历 C 是否有消息

  2. 给发送方发送消息,通知我已经开始焚烧 A 里的消息了 并在 A 容器删除此会话

  3. 发送方收到消息发送通知

  4. 接收方遍历 B 容器,判断每条消息是否到时

  5. 如果消息焚烧时间到 在 A、B 容器删除,并触发代理

  6. 如果没到时间,就触发代理并修改 此消息在 B 容器的时长。


收起阅读 »

融云 SDK 如何实现群组操作

融云 SDK 如何实现群组操作背景在集成融云 SDK 后,需要实现群组操作的消息通知。包括:群创建、销毁通知群公告通知群人员加入、退出通知群昵称修改通知...融云现有的 SDK 仅提供了 RCGroupNotificationMessage,内部封装...
继续阅读 »

融云 SDK 如何实现群组操作

背景

在集成融云 SDK 后,需要实现群组操作的消息通知。包括:

  • 群创建、销毁通知

  • 群公告通知

  • 群人员加入、退出通知

  • 群昵称修改通知

  • ...

融云现有的 SDK 仅提供了 RCGroupNotificationMessage,内部封装了几种简单的类型操作,且扩展性不强,无法完成现有的操作。

解决方案:

使用自定义消息重新来构建群组通知消息。

参考 sealtlak 中的 RCDGroupNotificationMessage,也是单独对群组操作内容,重新进行了封装处理

/*!
 群组通知消息
 */
@interface RCDGroupNotificationMessage : RCMessageContent

//操作名
@property (nonatomic, copy) NSString *operation;

//操作人
@property (nonatomic, copy) NSString *operatorUserId;

//操作对象
@property (nonatomic, strong) NSArray<NSString *> *targetUserIds;

//内容
@property (nonatomic, copy) NSString *message;

//获取摘要
- (NSString *)getDigest:(NSString *)groupId;
@end

这个类预定义了下面几种操作类型消息,具体内容可参考名字定义

extern NSString *const RCDGroupCreate;
extern NSString *const RCDGroupMemberAdd;
extern NSString *const RCDGroupMemberQuit;
extern NSString *const RCDGroupMemberKicked;
extern NSString *const RCDGroupRename;
extern NSString *const RCDGroupBulletin;
extern NSString *const RCDGroupOwnerTransfer;
extern NSString *const RCDGroupDismiss;
extern NSString *const RCDGroupMemberJoin;
extern NSString *const RCDGroupMemberManagerSet;
extern NSString *const RCDGroupMemberManagerRemove;
extern NSString *const RCDGroupMemberProtectionOpen;
extern NSString *const RCDGroupMemberProtectionClose;

.m 的实现还是按照自定义消息的实现进行处理。唯一有区别的是在获取摘要的方法,在这个方法中对消息内容进行了处理,根据操作名的不同,来对显示的内容进行各种适配。此处根据根据自己的业务进行修改。

- (NSString *)getDigest:(NSString *)groupId {

    NSString *content;

    //获取用户信息
    NSString *operationName = [self getDisplayNames:@[ self.operatorUserId?self.operatorUserId:@""] groupId:groupId];
    NSString *targetNames = [self getDisplayNames:self.targetUserIds groupId:groupId];

    //是否当前用户操作
    BOOL isMeOperate = NO;
    if ([self.operatorUserId isEqualToString:[RCIMClient sharedRCIMClient].currentUserInfo.userId]) {
        isMeOperate = YES;
    }

    //判断操作类型
    if ([self.operation isEqualToString:RCDGroupCreate]) {
        content =
            [NSString stringWithFormat:NSLocalizedStringFromTable(isMeOperate ? @"GroupHaveCreated" : @"GroupCreated",
                                                                  @"RongCloudKit", nil),
                                       operationName];
    } else if ([self.operation isEqualToString:RCDGroupMemberAdd]) {
        if (self.targetUserIds.count == 1 && [self.targetUserIds containsObject:self.operatorUserId]) {
            content = [NSString
                stringWithFormat:NSLocalizedStringFromTable(@"GroupJoin", @"RongCloudKit", nil), operationName];
        } else {
            content = [NSString
                stringWithFormat:NSLocalizedStringFromTable(isMeOperate ? @"GroupHaveInvited" : @"GroupInvited",
                                                            @"RongCloudKit", nil),
                                 operationName, targetNames];
        }
    } else if ([self.operation isEqualToString:RCDGroupMemberJoin]) {
        content =
            [NSString stringWithFormat:NSLocalizedStringFromTable(@"GroupJoin", @"RongCloudKit", nil), operationName];
    } else if ([self.operation isEqualToString:RCDGroupMemberQuit]) {
        content = [NSString stringWithFormat:NSLocalizedStringFromTable(isMeOperate ? @"GroupHaveQuit" : @"GroupQuit",
                                                                        @"RongCloudKit", nil),
                                             operationName];
    } else if ([self.operation isEqualToString:RCDGroupMemberKicked]) {
        content =
            [NSString stringWithFormat:NSLocalizedStringFromTable(isMeOperate ? @"GroupHaveRemoved" : @"GroupRemoved",
                                                                  @"RongCloudKit", nil),
                                       operationName, targetNames];
    } else if ([self.operation isEqualToString:RCDGroupRename]) {
        content = [NSString stringWithFormat:NSLocalizedStringFromTable(@"GroupChanged", @"RongCloudKit", nil),
                                             operationName, self.targetGroupName];
    } else if ([self.operation isEqualToString:RCDGroupDismiss]) {
        content =
            [NSString stringWithFormat:NSLocalizedStringFromTable(isMeOperate ? @"GroupHaveDismiss" : @"GroupDismiss",
                                                                  @"RongCloudKit", nil),
                                       operationName];
    } else if ([self.operation isEqualToString:RCDGroupOwnerTransfer]) {
        content = [NSString stringWithFormat:RCDLocalizedString(@"GroupHasNewOwner"), targetNames];
    } else if ([self.operation isEqualToString:RCDGroupMemberManagerSet]) {
        content = [NSString stringWithFormat:RCDLocalizedString(@"GroupSetManagerMessage"), targetNames];
    } else if ([self.operation isEqualToString:RCDGroupMemberProtectionOpen]) {
        content = RCDLocalizedString(@"openMemberProtection");
    } else if ([self.operation isEqualToString:RCDGroupMemberProtectionClose]) {
        content = [NSString stringWithFormat:RCDLocalizedString(@"closeMemberProtection"), operationName];
    } else {
        content = NSLocalizedStringFromTable(@"unknown_message_cell_tip", @"RongCloudKit", nil);
    }
    return content;
}

此外,还有一个获取名称的方法,用来维护用户信息。

- (NSString *)getDisplayNames:(NSArray<NSString *> *)userIds groupId:(NSString *)groupId


收起阅读 »

干货分享——使用融云通讯能力库 IMLib 实现单群聊的阅读回执

今天的干货分享是关于“阅读回执”功能,这是一个很普遍的功能,但是针对使用融云的 SDK 去实现,还是有些坑在等着我们的,下面就开始分(bì)享(kēng)喽~分享之前先做一些准备工作,先找到我们需要调用的接口文档文档:https://docs.rongclou...
继续阅读 »

今天的干货分享是关于“阅读回执”功能,这是一个很普遍的功能,但是针对使用融云的 SDK 去实现,还是有些坑在等着我们的,下面就开始分(bì)享(kēng)喽~

  1. 分享之前先做一些准备工作,先找到我们需要调用的接口文档

  2. 根据不同的会话类型以及消息的发送方和接收方,要分别处理

    • 单聊

      接收方 :在阅读消息后,调用 RCIMClient 类的发送阅读回执接口,参数如下:

      conversationType 单聊会话类型

      targetId 消息的会话 ID

      time 会话最后一条消息的发送时间(sentTime)

/*!
 发送某个会话中消息阅读的回执

 @param conversationType    会话类型
 @param targetId            会话 ID
 @param timestamp           该会话中已阅读的最后一条消息的发送时间戳
 @param successBlock        发送成功的回调
 @param errorBlock          发送失败的回调[nErrorCode: 失败的错误码]

 @discussion 此接口只支持单聊, 如果使用 IMLib 可以注册监听
 RCLibDispatchReadReceiptNotification 通知,使用 IMKit 直接设置RCIM.h
 中的 enabledReadReceiptConversationTypeList。

 @warning 目前仅支持单聊。

 @remarks 高级功能
 */
- (void)sendReadReceiptMessage:(RCConversationType)conversationType
                      targetId:(NSString *)targetId
                          time:(long long)timestamp
                       success:(void (^)(void))successBlock
                         error:(void (^)(RCErrorCode nErrorCode))errorBlock;

发送方:监听下面这个通知,在接收后修改消息的展示

/*!
 @const 收到已读回执的 Notification

 @discussion 收到消息已读回执之后,IMLib 会分发此通知。

 Notification 的 object 为 nil,userInfo 为 NSDictionary 对象,
 其中 key 值分别为 @"cType"、@"tId"、@"messageTime",
 对应的 value 为会话类型的 NSNumber 对象 、会话的 targetId 、已阅读的最后一条消息的 sendTime。
 如:
 NSNumber *ctype = [notification.userInfo objectForKey:@"cType"];
 NSNumber *time = [notification.userInfo objectForKey:@"messageTime"];
 NSString *targetId = [notification.userInfo objectForKey:@"tId"];
 NSString *fromUserId = [notification.userInfo objectForKey:@"fId"];

 收到这个消息之后可以更新这个会话中 messageTime 以前的消息 UI 为已读(底层数据库消息状态已经改为已读)。

 @remarks 事件监听
 */
FOUNDATION_EXPORT NSString *const RCLibDispatchReadReceiptNotification;

群聊

发送方

在发送消息 A 后,需要针对该消息发送回执请求,message 传之前发的消息 A

/*!
 请求消息阅读回执

 @param message      要求阅读回执的消息
 @param successBlock 请求成功的回调
 @param errorBlock   请求失败的回调[nErrorCode: 失败的错误码]

 @discussion 通过此接口,可以要求阅读了这条消息的用户发送阅读回执。

 @remarks 高级功能
 */
- (void)sendReadReceiptRequest:(RCMessage *)message
                       success:(void (^)(void))successBlock
                         error:(void (^)(RCErrorCode nErrorCode))errorBlock;

设置下面代理函数,在接收到发送方发来的阅读回执响应后,修改消息的展示

/*!
 IMlib消息接收的监听器

 @discussion
 设置IMLib的消息接收监听器请参考RCIMClient的setReceiveMessageDelegate:object:方法。

 @warning 如果您使用IMlib,可以设置并实现此Delegate监听消息接收;
 如果您使用IMKit,请使用RCIM中的RCIMReceiveMessageDelegate监听消息接收,而不要使用此监听器,否则会导致IMKit中无法自动更新UI!
 */
@protocol RCIMClientReceiveMessageDelegate <NSObject>

/*!
 消息已读回执响应(收到阅读回执响应,可以按照 messageUId 更新消息的阅读数)
 @param messageUId       请求已读回执的消息ID
 @param conversationType conversationType
 @param targetId         targetId
 @param userIdList 已读userId列表
 */
- (void)onMessageReceiptResponse:(RCConversationType)conversationType
                        targetId:(NSString *)targetId
                      messageUId:(NSString *)messageUId
                      readerList:(NSMutableDictionary *)userIdList;

接收方

设置下面代理函数,在接收到消息 A 后,还会接收到针对消息 A 的阅读回执请求

/*!
 IMlib消息接收的监听器

 @discussion
 设置IMLib的消息接收监听器请参考RCIMClient的setReceiveMessageDelegate:object:方法。

 @warning 如果您使用IMlib,可以设置并实现此Delegate监听消息接收;
 如果您使用IMKit,请使用RCIM中的RCIMReceiveMessageDelegate监听消息接收,而不要使用此监听器,否则会导致IMKit中无法自动更新UI!
 */
@protocol RCIMClientReceiveMessageDelegate <NSObject>

/*!
 请求消息已读回执(收到需要阅读时发送回执的请求,收到此请求后在会话页面已经展示该 messageUId 对应的消息或者调用
 getHistoryMessages 获取消息的时候,包含此 messageUId 的消息,需要调用 sendMessageReadReceiptResponse
 接口发送消息阅读回执)

 @param messageUId       请求已读回执的消息ID
 @param conversationType conversationType
 @param targetId         targetId
 */
- (void)onMessageReceiptRequest:(RCConversationType)conversationType
                       targetId:(NSString *)targetId
                     messageUId:(NSString *)messageUId;

在代理方法中,调用下面接口发送阅读回执响应给发送方

/*!
 发送阅读回执

 @param conversationType 会话类型
 @param targetId         会话 ID
 @param messageList      已经阅读了的消息列表
 @param successBlock     发送成功的回调
 @param errorBlock       发送失败的回调[nErrorCode: 失败的错误码]

 @discussion 当用户阅读了需要阅读回执的消息,可以通过此接口发送阅读回执,消息的发送方即可直接知道那些人已经阅读。

 @remarks 高级功能
 */
- (void)sendReadReceiptResponse:(RCConversationType)conversationType
                       targetId:(NSString *)targetId
                    messageList:(NSArray<RCMessage *> *)messageList
                        success:(void (^)(void))successBlock
                          error:(void (^)(RCErrorCode nErrorCode))errorBlock;

总结

阅读回执需要区分会话类型处理,且单聊的阅读回执是针对会话的,群聊的阅读回执是针对某一条消息的:

单聊:接收方阅读某个会话的消息后,发送阅读回执 ——发送方接到阅读回执,更新 UI

群聊:发送方发送消息 A, 针对该消息,发送阅读回执请求 —— 接收方实现监听代理,接收到消息 A 的阅读回执请求 —— 接收方发送阅读回执响应 —— 发送方收到阅读回执响应,更新 UI


收起阅读 »

自定义融云会话列表 cell 选中背景

项目用的融云,IMKit SDK(自带 UI),但是在使用会话列表的时候,cell 选中和长按的时候默认是灰色的。设计说需要改啊,那就研究一下如何修改吧。废话不多说,直接继承 RCConversationListViewController,然后重写以下方法1...
继续阅读 »

项目用的融云,IMKit SDK(自带 UI),但是在使用会话列表的时候,cell 选中和长按的时候默认是灰色的。设计说需要改啊,那就研究一下如何修改吧。废话不多说,直接继承 RCConversationListViewController,然后重写以下方法
1.以下代码是去掉选中颜色的

- (void)willDisplayConversationTableCell:(RCConversationBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
}

2.以下代码是重写颜色的,想配啥色请随意

- (void)willDisplayConversationTableCell:(RCConversationBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    UIView *backView = [[UIView alloc] init];
    backView.backgroundColor = [UIColor redColor];
    cell.selectedBackgroundView = backView;
}

SDK 开放出来的 .h 类对方法注释写的很详细,建议大家多看一下,这样可以快速集成,少走弯路。也是培养集成第三方库的好习惯。融云(www.rongcloud.cn)

收起阅读 »

使用融云 IM 点击最近聊天记录时跳转到 @ 自己的消息

有没有遇到过这样的问题,在最近聊天记录列表里面有 @ 你的消息,点列表里面对应的记录,进入聊天页面以后,跳到了最新接收到的消息,想要看 @ 自己的消息,还得可劲儿的下来去找,使用体验不好,想要改善的话,往下看。实现思路就是获取会话中 @ 自己的消息,把这条消息...
继续阅读 »

有没有遇到过这样的问题,在最近聊天记录列表里面有 @ 你的消息,点列表里面对应的记录,进入聊天页面以后,跳到了最新接收到的消息,想要看 @ 自己的消息,还得可劲儿的下来去找,使用体验不好,想要改善的话,往下看。

实现思路就是获取会话中 @ 自己的消息,把这条消息的时间传给聊天页面,然后再跳转,就可以跳转到这条消息了。

  1. 在 push 到会话页面之前,调 RCIMClient 类下面接口,获取 @ 自己的消息

/*! 获取会话中@提醒自己的消息 
@param conversationType    会话类型 
@param targetId            目标会话ID 
@discussion 此方法从本地获取被@提醒的消息(最多返回10条信息) 
@warning 使用 IMKit 注意在进入会话页面前调用,否则在进入会话清除未读数的接口 clearMessagesUnreadStatus: targetId: 以及 设置消息接收状态接口 setMessageReceivedStatus:receivedStatus:会同步清除被提示信息状态。 
*/
- (NSArray *)getUnreadMentionedMessages:(RCConversationType)conversationType targetId:(NSString *)targetId;
  1. 遍历得到数组,找到自己想要跳转到的消息,把消息的 sentTime 传给要跳转的聊天页面,再 push 到聊天页面。

/** 进入页面时定位的消息的发送时间 
@discussion 用于消息搜索之后点击进入页面等场景 
*/
@property (nonatomic, assign) long long locatedMessageSentTime;

示例代码

- (void)onSelectedTableRow:(RCConversationModelType)conversationModelType conversationModel:(RCConversationModel *)model atIndexPath:(NSIndexPath *)indexPath {
    if (model.conversationType == ConversationType_GROUP) {
        NSArray *msgs = [[RCIMClient sharedRCIMClient] getUnreadMentionedMessages:model.conversationType targetId:model.targetId];
        if (msgs.count > 0) {
            RCMessage *msg = msgs[0];
            RCConversationViewController *vc = [[RCConversationViewController alloc] initWithConversationType:model.conversationType targetId:model.targetId];
            vc.locatedMessageSentTime = msg.sentTime;
            [self.navigationController pushViewController:vc animated:YES];
        }
    }
}

代码接口文档:https://docs.rongcloud.cn/v4/views/im/ui/guide/private/list/event/ios.html#onSelectedTableRow

融云的官网:https://www.rongcloud.cn/

从融云的官网文档能够找到点击会话列表 cell 的回调方法,在该方法里获取 @ 自己的消息,如果有,将该消息的 sentTime 设置给聊天页面对象的 locatedMessageSentTime,再 push。

**注:示例代码中使用的聊天页面是融云 SDK 中的原始类,如果你自己继承了,就替换为你自己的类,别的就没啥了。


收起阅读 »

如何设置融云用户信息

IOS
最近在使用融云,由于第一次使用,遇到了一个小坑,在这里记录一下,希望能帮助到后续开发者问题是使用了融云的 IMKit 组件,也就是自带 UI 的,对于快速试错的产品来说,工期必须短,所以使用 IMKit 是非常方便的,省去了很大部分时间去搞界面。但是使用过程中...
继续阅读 »

最近在使用融云,由于第一次使用,遇到了一个小坑,在这里记录一下,希望能帮助到后续开发者

问题是使用了融云的 IMKit 组件,也就是自带 UI 的,对于快速试错的产品来说,工期必须短,所以使用 IMKit 是非常方便的,省去了很大部分时间去搞界面。但是使用过程中发现,没有用户的头像和昵称。后来经过阅读文档发现,需要设置“用户信息提供者”代理方法。SDK 在需要显示头像和昵称的时候,会通过这个代理找开发者索取用户信息,开发者只要遵循代理,且实现代理方法,返回用户信息即可。

上代码:

1.遵循代理

@interface AppDelegate () <RCIMUserInfoDataSource>
@end

2.设置代理

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //必须先初始化
    [[RCIM sharedRCIM] initWithAppKey:"开发者自己的 appkey"];
    [[RCIM sharedRCIM] connectWithToken:"当前用户的 token" dbOpened:^(RCDBErrorCode code) {
        } success:^(NSString *userId) {
        } error:^(RCConnectErrorCode errorCode) {
        }];
    //设置当前用户信息
    RCUserInfo *currentUser = [[RCUserInfo alloc] initWithUserId:@"tiezhu" name:@"铁柱" portrait:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1573646812313&di=116350f184eda99d91393304fa83a6ea&imgtype=0&src=http%3A%2F%2Fimg.jinse.com%2F712431_image3.png"];
    [RCIM sharedRCIM].currentUserInfo = currentUser;
    //设置代理
    [RCIM sharedRCIM].userInfoDataSource = self;
}

3.实现代理方法

- (void)getUserInfoWithUserId:(NSString *)userId completion:(void (^)(RCUserInfo *userInfo))completion {
    //这里最好是从开发者自己服务器获取用户信息,然后返回。此处仅为示例
    RCUserInfo *user = nil;
    if ([userId isEqualToString:@"tiezhu"]) {
        user = [[RCUserInfo alloc] initWithUserId:@"tiezhu" name:@"铁柱" portrait:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1570172426&di=01d14daa81f320235376d9c4dede0493&imgtype=jpg&er=1&src=http%3A%2F%2Fgss0.baidu.com%2F-vo3dSag_xI4khGko9WTAnF6hhy%2Fzhidao%2Fpic%2Fitem%2Fd788d43f8794a4c240e9466f0ef41bd5ac6e39af.jpg"];
    }
    if (completion) {
        completion(user);
    }
}

到此就搞定了兄嘚,值一杯秋天的奶茶

收起阅读 »

融云聊天页面长按消息后“翻译”功能的实现方法

IOS
项目要求实现“翻译”的功能,融云 SDK 本身没这个功能,所以只能曲线救国了,通过自定义消息来实现,下面是功能实现相关内容。资源链接:官网:https://www.rongcloud.cn/自定义消息文档:https://docs.rongcloud.cn/v...
继续阅读 »

项目要求实现“翻译”的功能,融云 SDK 本身没这个功能,所以只能曲线救国了,通过自定义消息来实现,下面是功能实现相关内容。

资源链接:

官网:https://www.rongcloud.cn/
自定义消息文档:https://docs.rongcloud.cn/v4/views/im/ui/guide/private/conversation/msgsend/ios.html#createcustom

实现思路

  1. 创建自定义 cell,与 SDK 内置的文本消息进行绑定。因为他们内置的文本消息 cell 不支持扩展显示翻译的内容,所以需要使用自定义 cell。

  2. 在聊天页面将自定义 cell 与内置的文本消息进行绑定。

  3. 重写长按消息 cell 的方法,判断如果是文本消息,增加“翻译”按钮。

  4. 点击“翻译”按钮,对文本内容进行翻译,并将翻译好的内容设置到数据源中。

  5. 刷新 UI,会触发自定义 cell 中的回调方法,在回调方法中重新设置高度,并添加 UI,对数据源中翻译好的内容进行展示。

代码部分

  1. 创建自定义 cell 继承于 RCTextMessageCell,m 文件中代码如下,具体效果可自行调整

#import "RCDTextMessageCell.h"
#define RCDScreenWidth [UIScreen mainScreen].bounds.size.width
#define Font_Size 16
#define Extra_BackgroupView_CornerRadius 6.f
@interface RCDTextMessageCell ()
//翻译内容的 Label
@property (strong, nonatomic) UILabel *extraLabel;
//翻译内容的背景图
@property (strong, nonatomic) UIView *extraBackgroundView;
@end
@implementation RCDTextMessageCell
+ (CGSize)sizeForMessageModel:(RCMessageModel *)model
      withCollectionViewWidth:(CGFloat)collectionViewWidth
         referenceExtraHeight:(CGFloat)extraHeight {
    //翻译好的内容
    NSString *extra = model.extra;
    CGSize superSize = [super sizeForMessageModel:model withCollectionViewWidth:collectionViewWidth referenceExtraHeight:extraHeight];
    if (extra.length > 0) {
        CGSize extraSize = [RCDTextMessageCell getTextLabelSize:extra];
        CGFloat finalHeight = superSize.height + extraSize.height;
        return CGSizeMake(superSize.width, finalHeight);
    }else {
        return superSize;
    }
}
//计算文本 size 的方法
+ (CGSize)getTextLabelSize:(NSString *)content {
    if ([content length] > 0) {
        float maxWidth = RCDScreenWidth -
        (10 + [RCIM sharedRCIM].globalMessagePortraitSize.width + 10) * 2 - 5 - 35;
        CGRect textRect = [content
                           boundingRectWithSize:CGSizeMake(maxWidth, 8000)
                           options:(NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin |
                                    NSStringDrawingUsesFontLeading)
                           attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:Font_Size]}
                           context:nil];
        textRect.size.height = ceilf(textRect.size.height);
        textRect.size.width = ceilf(textRect.size.width);
        return CGSizeMake(textRect.size.width + 5, textRect.size.height + 5);
    } else {
        return CGSizeZero;
    }
}
- (void)setDataModel:(RCMessageModel *)model {
    [super setDataModel:model];
    NSString *extra = model.extra;
    //为了防止复用问题的处理
    [self.extraLabel removeFromSuperview];
    [self.extraBackgroundView removeFromSuperview];
    //如果有翻译的内容,添加 UI,进行展示
    if (extra.length > 0) {
        CGSize extraSize = [[self class] getTextLabelSize:extra];
        CGRect superFrame = self.bubbleBackgroundView.frame;
        CGRect extraBackgroundViewFrame = CGRectZero;
        //判断消息方向,设置翻译背景图的 frame
        if (model.messageDirection == MessageDirection_SEND) {
            extraBackgroundViewFrame = CGRectMake(superFrame.origin.x + superFrame.size.width - extraSize.width - 15, superFrame.size.height + 3, extraSize.width + 10, extraSize.height + 6);
        } else {
            extraBackgroundViewFrame = CGRectMake(superFrame.origin.x + 5, superFrame.origin.y + superFrame.size.height + 3, extraSize.width + 10, extraSize.height + 6);
        }
        self.extraBackgroundView = [[UIView alloc] initWithFrame:extraBackgroundViewFrame];
        self.extraBackgroundView.backgroundColor = [UIColor whiteColor];
        self.extraBackgroundView.layer.cornerRadius = Extra_BackgroupView_CornerRadius;
        [self.messageContentView addSubview:self.extraBackgroundView];
        CGRect extraLabelFrame = CGRectMake(8, 3, extraSize.width, extraSize.height);
        self.extraLabel = [[UILabel alloc] initWithFrame:extraLabelFrame];
        self.extraLabel.font = [UIFont systemFontOfSize:Font_Size];
        self.extraLabel.numberOfLines = 0;
        [self.extraLabel setLineBreakMode:NSLineBreakByWordWrapping];
        [self.extraLabel setTextAlignment:NSTextAlignmentLeft];
        [self.extraBackgroundView addSubview:self.extraLabel];
        self.extraLabel.text = extra;
    }
}
@end

在聊天页面类导入并绑定自定义 cell :

#import "RCDTextMessageCell.h"
- (void)viewDidLoad {
    [super viewDidLoad];    
    [self registerClass:[RCDTextMessageCell class] forMessageClass:[RCTextMessage class]];
}

在聊天页面实现长按消息的“翻译”方法:

  • 创建一个属性用于暂存长按时候的 model。

@property (strong, nonatomic) RCMessageModel *longTouchModel;

重写长按消息 cell 的方法,判断如果是文本消息,增加“翻译”按钮,translate 是实现“翻译”的方法。

- (NSArray<UIMenuItem *> *)getLongTouchMessageCellMenuList:(RCMessageModel *)model {
    NSMutableArray<UIMenuItem *> *menuList = [[super getLongTouchMessageCellMenuList:model] mutableCopy];
    if ([model.content isKindOfClass:[RCTextMessage class]]) {
        UIMenuItem *forwardItem = [[UIMenuItem alloc] initWithTitle:@"翻译"
                                                             action:@selector(translate)];
        self.longTouchModel = model;
        [menuList addObject:forwardItem];
    }
    return menuList;
}

实现“翻译”方法,并将翻译好的内容设置到数据源中:

  • 代码仅供参考,翻译的方法还需要自己来实现,把最终翻译好的结果赋值给 self.longTouchModel.extra

  • 这里必须要提的是 cellSize = CGSizeZero,否则刷新 UI 不会改变 cell 的高度

  • 刷新 self.conversationMessageCollectionView

  • 如果翻译的是最后一条消息,需要滚动到底部,否则翻译的内容会被遮挡

- (void)translate {
    if (self.longTouchModel) {
        NSString *result = @"翻译后的结果。";
        RCTextMessage *txtMsg = (RCTextMessage *)self.longTouchModel.content;
        if ([txtMsg.content isEqualToString:@"How are you?"]) {
            result = @"你好吗?";
        } else if ([txtMsg.content isEqualToString:@"I’m fine."]) {
            result = @"我很好。";
        }
        self.longTouchModel.extra = result;
        self.longTouchModel.cellSize = CGSizeZero;
        [self.conversationMessageCollectionView reloadData];
        RCMessageModel *model = [self.conversationDataRepository lastObject];
        if (model.messageId == self.longTouchModel.messageId ) {
            [self scrollToBottomAnimated:YES];
        }
    }
}
  1. 刷新 UI 时,显示翻译的内容,自定义 cell 会回调设置 CGSize 的方法,改变 cell 高度,同时也会回调 setDataModel 方法,在这个方法中添加翻译的内容,具体可以参考自定义 cell 的 m 文件中代码,这里就不重复贴代码了。

到此为止,该功能已经算是搞定了,同样这个思路和处理方法也适用于“语音转文字”,把翻译功能换成语音转文字,cell 继承于 RCVoiceMessageCell 就完了,希望这篇分享能帮助到大家。


收起阅读 »

如何隐藏融云输入框语音按钮

项目中使用了融云自带页面的 IMKit SDK,产品需求是不需要输入框处的语音按钮。发现 SDK 接口还是比较强大的,但是需要认真的查看 .h 文件 API 注释。直接使用聊天页面的 chatSessionInputBarControl 属性即可.它内部有接口...
继续阅读 »

项目中使用了融云自带页面的 IMKit SDK,产品需求是不需要输入框处的语音按钮。发现 SDK 接口还是比较强大的,但是需要认真的查看 .h 文件 API 注释。直接使用聊天页面的 chatSessionInputBarControl 属性即可.它内部有接口可以设置输入框类型:

上代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.chatSessionInputBarControl setInputBarType:RCChatSessionInputBarControlDefaultType
                                                   style:RC_CHAT_INPUT_BAR_STYLE_CONTAINER_EXTENTION];
}


收起阅读 »