通知设置 新通知
融云会话页面刷新不及时问题
技术交流 • 徐凤年 发表了文章 • 0 个评论 • 80 次浏览 • 2021-01-11 15:14
项目用的融云 IMKit SDK,调试中发现收到消息的时候,不刷新,上拉一下才会显示。排查方法是直接使用 SDK 的会话页面,排除是子类代码的问题,替换后发现还是有此问题。后来和技术人员沟通发现是使用了 RCIMClient 中的初始化接口,这样会影响 UI 刷新的。替换为 RCIM 的初始化方法,问题解决!希望此文字可以帮助到后续开发者!
/*!
初始化融云SDK
@param appKey 从融云开发者平台创建应用后获取到的App Key
@discussion 您在使用融云SDK所有功能(包括显示SDK中或者继承于SDK的View)之前,您必须先调用此方法初始化SDK。
在App整个生命周期中,您只需要执行一次初始化。
@warning 如果您使用IMKit,请使用此方法初始化SDK;
如果您使用IMLib,请使用RCIMClient中的同名方法初始化,而不要使用此方法。
*/
- (void)initWithAppKey:(NSString *)appKey;
友情提示融云官网:(www.rongcloud.cn)
融云 IMKit 音频录制参数
技术交流 • 王叫兽 发表了文章 • 0 个评论 • 75 次浏览 • 2021-01-11 15:14
场景:
使用融云自带的界面进行语音消息的播放。
自己进行音频录制。
使用的融云的
RCHQMessage
问题:
语音消息 iOS 和 Android 不互通,接收到消息之后无法播放。
解决方案:
经过与融云开发者的确认,使用时必须保证如下录制参数:
iOS AVAudioRecorder 录制参数如下设置:
AVFormatIDKey : @(kAudioFormatMPEG4AAC_HE),
AVSampleRateKey : @(44100.0),
AVNumberOfChannelsKey : @1,
AVEncoderBitRateKey : @(16000)
Android MediaRecorder 录制参数如下:
setAudioSamplingRate(44100);
setAudioEncodingBitRate(16000);
setAudioChannels(1);
setAudioSource(MediaRecorder.AudioSource.MIC);
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
其他一些内容的使用可以自己去官网文档搜索:
融云文档:https://docs.rongcloud.cn/v4/
如何利用融云 IMLib 来实现一个阅后即焚功能
技术交流 • 柠檬^ 发表了文章 • 0 个评论 • 72 次浏览 • 2021-01-11 15:14
场景
项目需要在私聊中来实现一个阅后即焚的功能,即 A 用户给 B 用户发送消息,B 用户在进入聊天页面查看之后 A 用户删除此消息,B 用户开始进入倒计时,倒计时结束后,删除此消息。
思考
大体的梳理一下具体的逻辑
A -> B
B 进入会话页面
B 将此消息开始倒计时
通知 A 我已进行阅读
A 删除消息
从上面内容我们来大体的设计一下我们需要用户的技术
单例类
自定义消息,用来告诉 A 我已经开始阅读了,你删除吧
一个用于维护阅后即焚消息的管理类
一个存储 A 给 B 发送的所有的阅后即焚的消息的容器 A k 为 targetid ,v 为 messageIDs
一个存储每条阅后即焚消息的容器 B k 为 messageId, v 为当前消息还剩的倒计时时间。
一个用来存储所有阅后即焚消息的容器 C K:ID V:msg
两个处理队列 一个处理时间 一个处理消息
对外暴露接口
代理 接收方焚烧消息的每秒倒计时
通知 接收方收到对方已阅读某条消息的通知
详解
初始化我们的所有容器
收到消息,在合适的业务时机将此消息加入到焚烧队列
查询消息是否已经在焚烧队列
如果不在,添加到 A B C容器
执行倒计时
倒计时操作
遍历 C 是否有消息
给发送方发送消息,通知我已经开始焚烧 A 里的消息了 并在 A 容器删除此会话
发送方收到消息发送通知
接收方遍历 B 容器,判断每条消息是否到时
如果消息焚烧时间到 在 A、B 容器删除,并触发代理
如果没到时间,就触发代理并修改 此消息在 B 容器的时长。
融云 Flutter IM SDK 解析
技术交流 • 木土走召 发表了文章 • 0 个评论 • 79 次浏览 • 2021-01-11 15:14
最近准备使用融云的 Flutter SDK,所以顺便记录一下。
融云 Flutter IM SDK 地址:传送门
融云的 Flutter SDK 是基于 融云 IMLib 层做的封装,封装了 IMLib 的部分接口提供给 Flutter 开发者使用。此文章只介绍了 Flutter 层做的一些操作。
目录结构
整体 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
目录开始分析
这两个文件是核心文件,我们所有的调用接口都在这里。
rong_im_client.dart
是最新版本的接口类,我们只关心这个即可。
rong_im_plugin.dart
是旧版本的接口类,已经废弃。
common_define.dart
是定义了 SDK 内使用的所有枚举和状态码。
method_key.dart
是定义了 Flutter SDK 和原生层进行交互时标识的唯一的常量字符串。
然后就是剩下的三个文件目录:
info
: 其实就是对象 model 类
message:
这个是 Flutter SDK提供的所有内置的消息类型,包括聊天室KV消息、合并消息、文件消息、GIF消息、图片消息、位置消息、撤回消息、引用消息、图文消息、视频消息、文本消息、语音消息。
其中有一个比较特殊的 dart 类就是 message_content.dart
, 这个类是所有消息的基类。
每个消息都需要继承此类,并复写里面的方法进行编解码。
util
: 这个目录就是一个工具目录。
message_factory.dart
主要是进行消息封装,字典转模型,模型转字典,根本原因我猜就是 Flutter 和 原生仅有的交互类型限制。
type_util.dart
:略
以上就是 SDK 在 Flutter 层做的所有的操作,内容整体不多,而且所有代码都是开源的,看看基本上都了解。
与原生层的交互
整体都是通过 invokeMethod
与原生进行通信的,后面的key 就是之前说的常量字符串,用来保证唯一性。
而在 iOS
和 Android
都提供了对应的定义。
并通过触发 iOS 和 Android 层对应的方法来进行处理。
原生层
Android:
整体核心的处理都在 RCIMFlutterWrapper.java
这个类处理了 dart 代码传递过来的数据,并与融云 IMLib 的 SDK 进行了处理。
iOS:
同样,iOS 的核心代码也都在 RCIMFlutterWrapper.m
这个类处理。具体内容大家可以自己看一下。
到此整个内容我们也就大体明白了。
整体看来其实就是 flutter 中 rong_im_client.dart
和 原生的两个 Wrapper
类做交互,其他的都是助攻。
整体看来不难,搞起~
融云文档:传送门
融云 SDK 如何实现群组操作
技术交流 • 柠檬^ 发表了文章 • 0 个评论 • 66 次浏览 • 2021-01-11 15:14
融云 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
融云自定义消息不显示
技术交流 • 徐凤年 发表了文章 • 0 个评论 • 67 次浏览 • 2021-01-11 15:14
项目用的融云,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)
关于融云 SDK 在使用 p8 证书的坎坷~
技术交流 • 木土走召 发表了文章 • 0 个评论 • 68 次浏览 • 2021-01-11 15:14
新上的项目使用了融云的 IM SDK,但在项目集成 APNs 推送的时候,尝鲜使用了一下开发者后台的 p8 证书,此文记录使用 p8 的辛酸史~
P8 简介
官网给出了这种更 "快" 的推送通道: Establishing a Token-Based Connection to APNs,并且这个生成的这个 key 可以适用于当前账户的所有 APP,为开发人员省了不少力气。福音啊~
想想那一堆证书...... 脑阔痛!
辛酸史
起因是这样的,在融云开发者后台上传了 p8 之后,发现 debug 环境,一直无法收到推送,在经过和融云提供的推送文档进行严格的比对之后,发现没毛病啊~
最后终于在融云开发人员的帮助下找到了问题~,融云后台目前阶段只支持生产环境~ OMG,我打你信不~
区别
p8 是可以同时支持生产和测试环境的,那么为什么融云收不到呢~
让我们大胆猜测一下:
之前基于证书进行校验的时候,一套证书是基于开发者后台一个 AppKey 绑定的,那么我用了哪个 AppKey,后端就基于 AppKey 解析对应的证书,这样就可以发送到对应的 push 环境去了,那么问题来了?使用了 p8 之后,他怎么区分呢?
我也不知道~ 哈哈哈,但我猜测应该是没有解析都去走了生产环境,因为提示我环境不匹配~
苹果 APNs 服务
Development server: api.sandbox.push.apple.com:443
Production server: api.push.apple.com:443
干货分享——使用融云通讯能力库 IMLib 实现单群聊的阅读回执
技术交流 • 王叫兽 发表了文章 • 0 个评论 • 70 次浏览 • 2021-01-11 15:14
今天的干货分享是关于“阅读回执”功能,这是一个很普遍的功能,但是针对使用融云的 SDK 去实现,还是有些坑在等着我们的,下面就开始分(bì)享(kēng)喽~
分享之前先做一些准备工作,先找到我们需要调用的接口文档
根据不同的会话类型以及消息的发送方和接收方,要分别处理
单聊
接收方 :在阅读消息后,调用 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
如何设置融云用户信息
技术交流 • dht1212 发表了文章 • 0 个评论 • 80 次浏览 • 2021-01-08 11:35
最近在使用融云,由于第一次使用,遇到了一个小坑,在这里记录一下,希望能帮助到后续开发者
问题是使用了融云的 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);
}
}
到此就搞定了兄嘚,值一杯秋天的奶茶
融云聊天页面长按消息后“翻译”功能的实现方法
技术交流 • dht1212 发表了文章 • 0 个评论 • 84 次浏览 • 2021-01-08 11:35
项目要求实现“翻译”的功能,融云 SDK 本身没这个功能,所以只能曲线救国了,通过自定义消息来实现,下面是功能实现相关内容。
资源链接:
官网:https://www.rongcloud.cn/
自定义消息文档:https://docs.rongcloud.cn/v4/views/im/ui/guide/private/conversation/msgsend/ios.html#createcustom
实现思路
创建自定义 cell,与 SDK 内置的文本消息进行绑定。因为他们内置的文本消息 cell 不支持扩展显示翻译的内容,所以需要使用自定义 cell。
在聊天页面将自定义 cell 与内置的文本消息进行绑定。
重写长按消息 cell 的方法,判断如果是文本消息,增加“翻译”按钮。
点击“翻译”按钮,对文本内容进行翻译,并将翻译好的内容设置到数据源中。
刷新 UI,会触发自定义 cell 中的回调方法,在回调方法中重新设置高度,并添加 UI,对数据源中翻译好的内容进行展示。
代码部分
创建自定义 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];
}
}
}
刷新 UI 时,显示翻译的内容,自定义 cell 会回调设置 CGSize 的方法,改变 cell 高度,同时也会回调 setDataModel 方法,在这个方法中添加翻译的内容,具体可以参考自定义 cell 的 m 文件中代码,这里就不重复贴代码了。
到此为止,该功能已经算是搞定了,同样这个思路和处理方法也适用于“语音转文字”,把翻译功能换成语音转文字,cell 继承于 RCVoiceMessageCell 就完了,希望这篇分享能帮助到大家。