融云 SDK 如何实现群组操作

IM即时通讯柠檬^ 发表了文章 • 0 个评论 • 202 次浏览 • 2021-01-11 15:14 • 来自相关话题

融云 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 实现单群聊的阅读回执

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 218 次浏览 • 2021-01-11 15:14 • 来自相关话题

今天的干货分享是关于“阅读回执”功能,这是一个很普遍的功能,但是针对使用融云的 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 选中背景

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 223 次浏览 • 2021-01-08 11:35 • 来自相关话题

项目用的融云,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即时通讯dht1212 发表了文章 • 0 个评论 • 221 次浏览 • 2021-01-08 11:35 • 来自相关话题

最近在使用融云,由于第一次使用,遇到了一个小坑,在这里记录一下,希望能帮助到后续开发者问题是使用了融云的 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);
    }
}

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

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

IM即时通讯柠檬^ 发表了文章 • 0 个评论 • 216 次浏览 • 2021-01-08 11:35 • 来自相关话题

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

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

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

  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 中的原始类,如果你自己继承了,就替换为你自己的类,别的就没啥了。


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

IM即时通讯dht1212 发表了文章 • 0 个评论 • 227 次浏览 • 2021-01-08 11:35 • 来自相关话题

项目要求实现“翻译”的功能,融云 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 就完了,希望这篇分享能帮助到大家。


给融云的输入框上方加个功能按钮,怎么整?

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 218 次浏览 • 2021-01-08 11:29 • 来自相关话题

给输入框上方加个功能按钮,类似常用语或者抽奖啥的,是个挺普遍的需求,可惜遍寻文档(https://docs.rongcloud.cn/v4/)无果,只能靠自己了,咱们来看看怎么做吧。首先,我们要先在聊天页面添加个属性,也就是需要功能按钮所在的 view@pro... ...查看全部

给输入框上方加个功能按钮,类似常用语或者抽奖啥的,是个挺普遍的需求,可惜遍寻文档(https://docs.rongcloud.cn/v4/)无果,只能靠自己了,咱们来看看怎么做吧。

首先,我们要先在聊天页面添加个属性,也就是需要功能按钮所在的 view

@property (nonatomic, strong) UIView *needAddView;

再就是需要重写 viewWillAppear 生命周期函数,添加这个 needAddView,设置 UI 布局,保证进入页面时,needAddView 可以正确显示

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    //初始化 needAddView,添加到 self.view 上,坐标 y = 输入框的 y 坐标 - needAddView 高度
    CGFloat needAddView_height = 50.f;
    CGFloat y = self.chatSessionInputBarControl.frame.origin.y - needAddView_height;
    self.needAddView = [[UIView alloc] initWithFrame:CGRectMake(0, y, self.conversationMessageCollectionView.frame.size.width, needAddView_height)];
    self.needAddView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:self.needAddView];
    //设置消息内容 collectionView 的高度,要减去 needAddView 的高度,避免被遮挡。
    CGRect frame = self.conversationMessageCollectionView.frame;
    frame.size.height -= needAddView_height;
    self.conversationMessageCollectionView.frame = frame;
}

最后需要根据输入框的位置变化,对 UI 布局做改变

-(void)chatInputBar:(RCChatSessionInputBarControl *)chatInputBar shouldChangeFrame:(CGRect)frame {
    //切记要调用父类方法,保证 UI 布局显示正确
    [super chatInputBar:chatInputBar shouldChangeFrame:frame];
    //needAddView 的坐标 y = 输入框的 y 坐标 - needAddView 高度。回调方法中的 frame 是输入框改变后的值。
    CGRect viewFrame = self.needAddView.frame;
    viewFrame.origin.y = frame.origin.y - viewFrame.size.height;
    self.needAddView.frame = viewFrame;
    //设置消息内容 collectionView 的高度,要减去 needAddView 的高度,避免被遮挡。
    CGRect collectionViewFrame = self.conversationMessageCollectionView.frame;
    collectionViewFrame.size.height -= self.needAddView.frame.size.height;
    self.conversationMessageCollectionView.frame = collectionViewFrame;
    //重新设置消息内容 collectionView 的 ContentOffset,正常显示消息内容。
    if (self.conversationMessageCollectionView.contentSize.height > collectionViewFrame.size.height) {
        [self.conversationMessageCollectionView setContentOffset:CGPointMake(0, self.conversationMessageCollectionView.contentSize.height - collectionViewFrame.size.height) animated:NO];
    }
}

最后再提一句,如果有类似的功能需求实现不了的,可以去融云官网(https://www.rongcloud.cn/),登录后台提工单,他们会有专人给出解决方案。

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

IM即时通讯王叫兽 发表了文章 • 0 个评论 • 214 次浏览 • 2021-01-08 11:29 • 来自相关话题

项目中使用了融云自带页面的 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];
}


集成融云 IMLib 时,如何实现一套类似于 IMKit 的用户信息管理机制

IM即时通讯柠檬^ 发表了文章 • 0 个评论 • 208 次浏览 • 2021-01-08 11:15 • 来自相关话题

背景我们在设计聊天类 APP 都会有一套完整的用户信息存储机制,用来保存我们的通讯录列表,以及每个用户的头像、昵称、姓名、等等一系列的用户信息,防止我们过多的进行服务器请求,对用户体验很差。这篇文章就简单的给大家创建一套用户信息机制来提供一个简单的思路。场景如... ...查看全部

背景

我们在设计聊天类 APP 都会有一套完整的用户信息存储机制,用来保存我们的通讯录列表,以及每个用户的头像、昵称、姓名、等等一系列的用户信息,防止我们过多的进行服务器请求,对用户体验很差。这篇文章就简单的给大家创建一套用户信息机制来提供一个简单的思路。

场景如下:
目前我们集成了融云的 IMLib SDK , 融云 IMLib SDK 仅提供了消息数据的存储与查询。用户信息和 UI 界面需要我们自己来维护,而融云的 IMKit 虽然提供了用户信息的管理,但是部分 UI 还是和我们产品设计不符的,那么如何设计一套类似于 IMKit 的用户信息管理机制,就是我们面临的问题。

融云SDK:
https://docs.rongcloud.cn/v4/

思考

我们在实现这套机制的时候都需要哪些内容?

  1. 首先我们要进行存储,存储那就需要维护一个数据库。参考融云 IMKit 发现有下面一个配置

/*!
 是否将用户信息和群组信息在本地持久化存储,默认值为NO
 @discussion
 如果设置为NO,则SDK在需要显示用户信息时,会调用用户信息提供者获取用户信息并缓存到Cache,此Cache在App生命周期结束时会被移除,下次启动时会再次通过用户信息提供者获取信息。
 如果设置为YES,则会将获取到的用户信息持久化存储在本地,App下次启动时Cache会仍然有效。
 */
@property (nonatomic, assign) BOOL enablePersistentUserInfoCache;

经过测试以及融云专业技术人员的回答,发现这个配置起到的作用就是本次的用户信息是否会进行数据库存储。

这里的数据库存储是指当此配置生效时,会在本地进行数据库文件的创建。而不生效的时候,是不创建的。如果不创建的话还需要存储的话,那应该就是存储到内存了。 而我们是需要每次登陆都有一些用户信息的,那不需要使用内存了,需要我们进行数据库存储,所以我们需要准备一个 db 的管理类,处理所有的数据库操作。

实现

以 融云 SDK 为例,以一个用户的 userinfo 为例大体来绘制一下整个流程图。

首先整理一些各个类的用处,大体内容如下:

UserInfoDBHelper: 数据库管理

#import <Foundation/Foundation.h>
#import "UserInfo.h"
NS_ASSUME_NONNULL_BEGIN
@interface UserInfoDBHelper : NSObject
- (void)createDB;
- (UserInfo *)getUserInf(NSString *)userId;
- (void)refreshUserInf(UserInfo *)userInfo;
@end
NS_ASSUME_NONNULL_END

UserInf 用户信息模型

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface UserInfo : NSObject
/**
 id
 name
 url
 */
@end
NS_ASSUME_NONNULL_END


UserInfoCache: 用户信息读取类

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface UserInfoCache : NSObject
- (UserInfo *)getUserInf(NSString *)userId;
- (void)refreshUserInf(UserInfo *)userInfo;
@end
NS_ASSUME_NONNULL_END

UserInfoManager: 对外入口

#import <Foundation/Foundation.h>
#import "UserInfo.h"
NS_ASSUME_NONNULL_BEGIN
@protocol UserInfoDelegate <NSObject>
- (void)getServerUserInf(NSString *)userid;
@end
@interface UserInfoManager : NSObject
@property (nonatomic, weak) id<UserInfoDelegate> delegate;
- (UserInfo *)getUserInf(NSString *)userid;
- (void)refreshUserInf(UserInfo *)userInfo;
@end
NS_ASSUME_NONNULL_END

此处提供了一个简单的流程图,供大家参考

image.png

解析:

  1. 首先用户登录,

  2. 绘制 UI,同时根据用户的 id 来调用 manager.getuserinfo。

  3. manager 会调用用户的 cache 类来获取 db 内的用户信息。

  4. 在 cache 调用的时候如果你进行了内存缓存则先在内存缓存中进行查找,然后调用 db.get 进行查找

  5. db 在首次使用的时候会进行数据库文件检查,不存在则创建并直接用户信息返回空。然后调用
    manager 的 delegate 获取用户信息,再拿到用户信息后发送通知来刷新 UI。
    6.如果已创建的直接调用 db 的 get。然后刷新 UI。

整体先只提供了一个思路,具体里面的一些内部优化还需实践。

可能用到的技术点:

  1. SQL 的使用

  2. 单例类的使用

  3. GCD 队列

  4. 通知

  5. 代理

  6. 如果使用内存缓存的话还需要保证线程安全。

整体的一个简单思路就是上面供大家参考,待我写完再分享整体代码给大家。


为融云聊天页面的输入框添加 Placeholder

IM即时通讯柠檬^ 发表了文章 • 0 个评论 • 207 次浏览 • 2021-01-08 11:15 • 来自相关话题

产品要求给输入框加个Placeh,其实挺简单一功能,寻遍他们的官网https://www.rongcloud.cn/和文档https://docs.rongcloud.cn/v4/都没有找到相关资料,现实很残酷,SDK 木有这个接口,只能自己实现了,... ...查看全部

产品要求给输入框加个Placeh,其实挺简单一功能,寻遍他们的官网https://www.rongcloud.cn/和文档https://docs.rongcloud.cn/v4/都没有找到相关资料,现实很残酷,SDK 木有这个接口,只能自己实现了,思来想去,用了个笨办法,加个 UILabel 一试,还真行,有需要的您请往下看。

其实就是给输入框价格 UILabel,在该显示的时候显示,该隐藏的时候隐藏就完事儿了,代码如下:

  1. 在聊天页面添加一个 UILabel 属性

@property(nonatomic, strong) UILabel *placeholderLabel;
  1. 初始化并添加 placeholderLabel 对象

- (void)configPlaceholder {    
    //初始化和设置    
    self.placeholderLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 180, 20)];
    [self.chatSessionInputBarControl.inputTextView addSubview:self.placeholderLabel];
    self.placeholderLabel.text = @"测试 Placeholder";    
    self.placeholderLabel.textColor = [UIColor grayColor];    
    self.placeholderLabel.userInteractionEnabled = YES;    
    //添加点击手势    
    UITapGestureRecognizer *tapLabel = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapPlaceholderLabel)];    
    [self.placeholderLabel addGestureRecognizer:tapLabel];
}
- (void)tapPlaceholderLabel {    
    [self.chatSessionInputBarControl updateStatus:KBottomBarKeyboardStatus animated:YES];
}
  1. 在内容发生变化和点击发送后,设置 placeholder 效果的显示

- (void)inputTextView:(UITextView *)inputTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {    
    //在内容发生变化和点击发送后,设置 placeholder 效果的显示    
    if ((range.location == 0 && [text isEqualToString:@""]) || [text isEqualToString:@"
"]) {        
        self.placeholderLabel.hidden = NO;    
        } else {        
        self.placeholderLabel.hidden = YES;    
        }
}
  1. 还要提一下,融云的 SDK 有撤回消息以后的“重新编辑”功能,在这个时候,要关闭 placeholder 效果的显示

- (void)didTapReedit:(RCMessageModel *)model {    
    self.placeholderLabel.hidden = YES;    
    [super didTapReedit:model];
}

到这儿功能就完成了,placeholderLabel 的具体文字效果可以自行修改调整,希望上面的代码可以帮到你,喜欢的话,点个赞吧。

友情链接