融云聊天之iOS笔记摘录
2018-04-21 本文已影响0人
平安喜乐698
目录
1. 概念
2. 集成融云
3. 会话列表页(自定义CELL)
聊天页(自定义消息、CELL)
4. 用户信息、小角标、消息类型
1. 概念
融云SDK的系统架构
IMKit
封装了各种界面对象。
用于快速集成。
IMLib
封装了通信能力和 Conversation,Message 等各种对象,提供基本通信能力库。
用于高度自定义UI。
Protocol
融云的核心协议栈,使用融云自定义的私有二进制协议
![](https://img.haomeiwen.com/i5111884/00e02f2a6f989f8c.png)
相关名词
通知
提示用户的方式,消息、角标、弹窗
推送
从服务器发送消息到前端的技术
广播
向所有客户端发送一条消息
应用切换到后台2分钟后,将自动切断与服务器的连接,变为离线状态。
用户A向用户B发送消息:
首先,应用会将消息传递给融云服务器,如果用户B在线则将消息传给用户B,如果用户B不在线,则将推送消息给用户B绑定的设备。
2. 集成融云
第一步:
苹果开发者中心创建Identify,打开推送功能,创建测试、发布推送证书,下载双击安装并导出为p12格式。
融云开发者官网创建应用,上传2个p12证书,拿到Key和Sect
第二步:
项目|CapBilities|打开通知功能,并勾选Background Mode下的Remote notifications
cocoaPods 引入
# 聊天
pod 'RongCloudIM/IMLib'
pod 'RongCloudIM/IMKit'
第三步:
AppDelegate配置
#import <RongIMKit/RongIMKit.h>
didFinish方法中+注册融云、注册APNs(注册成功后将token传给融云)
// 注册融云
[[RCIM sharedRCIM] initWithAppKey:RongAppKeyStore];
// 远程推送的内容(点击通知栏的通知,且当App被杀死时)
NSDictionary *remoteNotificationUserInfo = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
实现前台、后台收到消息后的回调(处于前台、处于后台点击通知会分别调用相应的方法,程序销毁点击通知会将通知消息存放在didFinishLaunchingWithOptions的launchOptions参数中)
登录融云
// 链接服务器---昵称/头像/id
YTAccount *acount=[YTAccountTool account];
NSDictionary *paramDic=@{@"userId":acount.userId,@"portraitUri":acount.iconurl,@"name":acount.name};
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
// 下面是按融云的提示写的
NSString * timestamp = [[NSString alloc] initWithFormat:@"%ld",(NSInteger)[NSDate timeIntervalSinceReferenceDate]];
NSString * nonce = [NSString stringWithFormat:@"%d",arc4random()];
NSString * appkey = RongAppKeyStore;
NSString *SignatureWillMD5 = [NSString stringWithFormat:@"%@%@%@",appkey,nonce,timestamp];//这个要加密
// NSString *Signature = [self MD5String:@"aaaaa"];
// 以下拼接请求内容
[manager.requestSerializer setValue:appkey forHTTPHeaderField:@"App-Key"];
[manager.requestSerializer setValue:nonce forHTTPHeaderField:@"Nonce"];
[manager.requestSerializer setValue:timestamp forHTTPHeaderField:@"Timestamp"];
[manager.requestSerializer setValue:SignatureWillMD5 forHTTPHeaderField:@"Signature"];
[manager.requestSerializer setValue:RongAppSecStore forHTTPHeaderField:@"appSecret"];
[manager.requestSerializer setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
// 开始请求
[manager POST:kCustomerRCTOKEN parameters:paramDic progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSString *token=responseObject[@"token"];
// success/error/tokenIncorrent三者只会调用一个且仅调用一次
[[RCIM sharedRCIM]connectWithToken:token success:^(NSString *userId) {
// success
NSLog(@"登录融云成功,token:%@",token);
/*
// 设置当前登陆用户
RCUserInfo *userInfo=[RCUserInfo new];
[userInfo setName:@""];
[userInfo setUserId:@""];
[userInfo setPortraitUri:@""];
[[RCIM sharedRCIM]setCurrentUserInfo:userInfo];
[RCIM sharedRCIM].receiveMessageDelegate=self;
*/
} error:^(RCConnectErrorCode status) {
// 出错了
NSLog(@"登录错误码%ld",(long)status);
// 除了以下错误,融云都会自动重练
// AppKey出错 :RC_CONN_ID_REJECT = 31002
// Token无效(前后台不一致,token过期) :RC_CONN_TOKEN_INCORRECT = 31004
// BundleID 不正确 :RC_CONN_PACKAGE_NAME_INVALID = 31007
// App Key 被封禁或已删除 :RC_CONN_APP_BLOCKED_OR_DELETED = 31008
// 用户被封禁 :RC_CONN_USER_BLOCKED = 31009
// 当前用户在其他设备上登录,此设备被踢下线 :RC_DISCONN_KICK = 31010
// SDK 没有初始化 :RC_CLIENT_NOT_INIT = 33001
// 接口调用时传入的参数错误 :RC_INVALID_PARAMETER = 33003,RC_INVALID_ARGUMENT = -1000
} tokenIncorrect:^{
// 获取token失败,再次获取一次
NSLog(@"token error");
//
[[RCIM sharedRCIM]connectWithToken:token success:^(NSString *userId) {
//
NSLog(@"登录融云成功,token:%@",token);
[RCIM sharedRCIM].receiveMessageDelegate=self;
} error:^(RCConnectErrorCode status) {
//
NSLog(@"登录错误码%ld",(long)status);
} tokenIncorrect:^{
// 获取token失败,不用再次调用(避免循环调用)
NSLog(@"token error");
}];
}];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"获取融云TOKEN失败%@",error);
}];
断开融云
一般不需要手动断开,因为:SDK在前后台切换或者网络出现异常都会自动断开或重连。
方式一:断开连接之后是否接收远程推送
[[RCIM sharedRCIM]disconnect:true];
方式二:断开连接之后仍接收远程推送
[[RCIM sharedRCIM]disconnect];
方式三:断开连接之后不接收远程推送
[[RCIM sharedRCIM]logout];
会话列表页
继承RCConversationListViewController (类似tableViewController)
聊天内容页
继承RCConversationViewController (类似collectionViewController)
3. IMKit
会话列表页自定义UI样式:
1、覆写willDisplayConversationTableCell方法(不能高度自定制)
2、直接自定义CELL (允许高度自定制)
3.1 会话列表页(自定义CELL)
1.会话列表页 YTConversationListController
YTConversationListController.h
#import <RongIMKit/RongIMKit.h>
@interface YTConversationListController : RCConversationListViewController
@end
YTConversationListController.m
#import "YTConversationListController.h"
#import "YTMessageViewController.h"
#import "YTRCListCell.h"
@interface YTConversationListController ()
@end
@implementation YTConversationListController
// 更新未读消息角标
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.conversationListTableView reloadData];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
}
// UI
-(void)setupUI{
self.navigationItem.title=@"消息";
// 设置需要显示的会话类型
[self setDisplayConversationTypes:@[@(ConversationType_PRIVATE)]];
ConversationType_PRIVATE 单聊
ConversationType_DISCUSSION 讨论组
ConversationType_CHATROOM 聊天室
ConversationType_GROUP 群组
ConversationType_SYSTEM 系统会话(只能由服务器发起)
ConversationType_APPSERVICE 应用内公众服务会话
ConversationType_PUBLICSERVICE 跨应用公众服务会话
ConversationType_PUSHSERVICE 推送服务会话
ConversationType_CUSTOMERSERVICE 客服
// 设置需要显示的聚合会话类型
[self setCollectionConversationType:@[@(ConversationType_DISCUSSION),
@(ConversationType_GROUP)]];
// 是否显示 无网络时的提示(默认:true)
[self setIsShowNetworkIndicatorView:false];
// �没有消息时显示的bgView
[self setEmptyConversationView:bgView];
// cell bgColor
[self setCellBackgroundColor:[UIColor blueColor]];
// 置顶Cell bgColor
[self setTopCellBackgroundColor:[UIColor redColor]];
// conversationListTableView继承自UITableView
// 设置 头、尾视图
[self.conversationListTableView setTableHeaderView:[UIView new]];
[self.conversationListTableView setTableFooterView:[UIView new]];
// 分割线
[self.conversationListTableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
// bgColor
[self.conversationListTableView setBackgroundColor:[UIColor whiteColor]];
// 内间距
[self.conversationListTableView setContentInset:UIEdgeInsetsMake(10, 0, 0, 0)];
// 自定义CELL
[self.conversationListTableView registerClass:[YTRCListCell class] forCellReuseIdentifier:NSStringFromClass([YTRCListCell class])];
/*
// 头像style (默认;矩形,圆形)
[[RCIM sharedRCIM]setGlobalMessageAvatarStyle:RC_USER_AVATAR_CYCLE];
// 头像size(默认:46*46,必须>36*36)
[[RCIM sharedRCIM]setGlobalMessagePortraitSize:CGSizeMake(46, 46)];
// 个人信息,自定义后不再有效。没自定义CELL时可使用,并实现getUserInfoWithUserId代理方法(详见聊天页)
[[RCIM sharedRCIM]setUserInfoDataSource:self];
*/
// 推送
[self setupPush];
}
// 如果关闭推送,弹框去设置推送
-(void)setupPush{
//
if ([[UIDevice currentDevice].systemVersion floatValue]>=8.0f) {
UIUserNotificationSettings *setting = [[UIApplication sharedApplication] currentUserNotificationSettings];
if (UIUserNotificationTypeNone == setting.types) {
//
NSLog(@"推送关闭 8.0");
YTRecommendView *recV=[YTRecommendView ViewWithTitle:@"您关闭了系统通知" withContent:@"开启系统通知,以免错过重要消息" withImgName:@"orderG" withButtonTitle:@"去开启"];
recV.goBlock=^{
// 跳手机系统的 通知设置
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
};
}else{
NSLog(@"推送打开 8.0");
}
}else{
//
UIRemoteNotificationType type = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
if(UIRemoteNotificationTypeNone == type){
NSLog(@"推送关闭");
YTRecommendView *recV=[YTRecommendView ViewWithTitle:@"您关闭了系统通知" withContent:@"开启系统通知,以免错过重要消息" withImgName:@"orderG" withButtonTitle:@"去开启"];
recV.goBlock=^{
// 跳手机系统的 通知设置
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
};
}else{
NSLog(@"推送打开");
}
}
}
#pragma mark 自定义CELL 以下方法按需求去实现
// dataSource (修改数据源来修改UI)(在上方新增自定义cell)
// 要更换为RC_CONVERSATION_MODEL_TYPE_CUSTOMIZATION否则不调用覆写方法)
-(NSMutableArray *)willReloadTableData:(NSMutableArray *)dataSource {
RCConversationModel *model=dataSource[0];
[model setConversationModelType:RC_CONVERSATION_MODEL_TYPE_CUSTOMIZATION];
// 修改model
// ...
return dataSource;
}
// height
- (CGFloat)rcConversationListTableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 95;
}
// 每行的编辑格式
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath.row==0){
return UITableViewCellEditingStyleNone;
}else{
return UITableViewCellEditingStyleDelete;
}
}
// 编辑后调用
-(void)rcConversationListTableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
// 服务器删除
RCConversationModel *model = self.conversationListDataSource[indexPath.row];
[[RCIMClient sharedRCIMClient] removeConversation:ConversationType_PRIVATE
targetId:model.targetId];
// UI本地删除
[self.conversationListDataSource removeObjectAtIndex:indexPath.row];
[self.conversationListTableView reloadData];
}
// cell
- (RCConversationBaseCell *)rcConversationListTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// model-dataSource
RCConversationModel *model = self.conversationListDataSource[indexPath.row];
// 请求个人信息---自己的服务器
__weak YTConversationListController *weakSelf = self;
if(!model.extend && model.lastestMessage){
//
[LHAFNetWork POST:YTBaseUrl(kCustomerMessage) params:@{@"userId":model.targetId} success:^(NSURLSessionDataTask *task, id responseObject) {
if(SUCCESS){
//
NSDictionary *dic=(NSDictionary *)responseObject[@"data"];
if([dic isEqual:[NSNull null]]){
//
return;
}
//
YTAccount *acount=[YTAccountTool account];
acount.userId=dic[@"id"];
acount.name=dic[@"name"];
acount.iconurl=YTImgBaseUrl(dic[@"photo"]);
//
RCUserInfo *userInfo = [[RCUserInfo alloc]init];
userInfo.userId=acount.userId;
userInfo.name=acount.name;
userInfo.portraitUri=acount.iconurl;
model.extend=userInfo;
[weakSelf.conversationListTableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
} fail:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"%@",error);
}];
}
//
YTRCListCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([YTRCListCell class])];
[cell setConvM:model withIsFirst:indexPath.row==0?true:false withUserInfo:model.extend];
return cell;
}
// will display
- (void)willDisplayConversationTableCell:(RCConversationBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath{
// 选中不高亮(在cell中设置无效)
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
// 获取Model会话类型,做其它处理
RCConversationModel *model=cell.model;
if(model.conversationModelType != RC_CONVERSATION_MODEL_TYPE_CUSTOMIZATION){
// 必须强制转换
RCConversationCell *RCcell = (RCConversationCell *)cell;
// 是否未读消息数(头像右上角,默认:true)
[RCcell setIsShowNotificationNumber:true];
RCcell.conversationTitle.font = [UIFont fontWithName:@"PingFangSC-Light" size:18];
RCcell.messageContentLabel.font = [UIFont fontWithName:@"PingFangSC-Light" size:16];
RCcell.messageCreatedTimeLabel.font = [UIFont fontWithName:@"PingFangSC-Light" size:14];
}
}
// 点击cell跳-重写RCConversationListViewController的onSelectedTableRow事件
- (void)onSelectedTableRow:(RCConversationModelType)conversationModelType
conversationModel:(RCConversationModel *)model
atIndexPath:(NSIndexPath *)indexPath {
// 跳转到 聊天内容页
YTMessageViewController *conversationVC = [YTMessageViewController new];
conversationVC.conversationType = model.conversationType; // 数据源type:单聊...
conversationVC.title = ((RCUserInfo *)model.extend).name; // 标题:对方昵称
conversationVC.targetId = model.targetId; // 对方ID
conversationVC.isNoHave=true;
[self.navigationController pushViewController:conversationVC animated:YES];
}
// onRCIMReceiveMessage。收到消息---更新未读角标
-(void)onRCIMReceiveMessage:(RCMessage *)message left:(int)left{
//
[self.conversationListTableView reloadData];
}
@end
- 自定义CELL页 YTRCListCell
YTRCListCell.h
#import <RongIMKit/RongIMKit.h>
@class RCConversationModel;
@interface YTRCListCell : RCConversationBaseCell
-(void)setConvM:(RCConversationModel *)convM withIsFirst:(BOOL)isF withUserInfo:(RCUserInfo *)info;
@end
YTRCListCell.m
#import "YTRCListCell.h"
#import "YTConvertToTimeTool.h"
@interface YTRCListCell()
@property (nonatomic,strong) UILabel *nameL;
@property (nonatomic,strong) UILabel *contentL;
@property (nonatomic,strong) UILabel *timeL;
@property (nonatomic,strong) UIImageView *photoImgV;
@property (nonatomic,strong) UILabel *numL;
@end
@implementation YTRCListCell
//
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
//
if(self=[super initWithStyle:style reuseIdentifier:reuseIdentifier]){
//
[self setSelectionStyle:UITableViewCellSelectionStyleNone]; // 此处设置无效,willdisplay中设置
[self setupUI];
}
return self;
}
//
-(void)setupUI{
// bg
UIImageView *bgImgV=[UIImageView new];
[bgImgV setImage:[UIImage imageNamed:@"messageList"]];
[bgImgV setContentMode:UIViewContentModeTop];
[self addSubview:bgImgV];
[bgImgV autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsMake(0, 10, 10, 10)];
// photo
UIImageView *photoImgV=[UIImageView new];
_photoImgV=photoImgV;
[photoImgV.layer setMasksToBounds:true];
[photoImgV.layer setCornerRadius:50/2];
[bgImgV addSubview:photoImgV];
[photoImgV autoAlignAxisToSuperviewAxis:ALAxisHorizontal];
[photoImgV autoSetDimension:ALDimensionWidth toSize:50];
[photoImgV autoSetDimension:ALDimensionHeight toSize:50];
[photoImgV autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:5];
// name
UILabel *nameL=[UILabel new];
_nameL=nameL;
[nameL setFont:YTFONT_PF_S(15)];
[nameL setTextColor:YTColorFromRGB(0x414141)];
[bgImgV addSubview:nameL];
[nameL autoPinEdge:ALEdgeTop toEdge:ALEdgeTop ofView:photoImgV];
[nameL autoPinEdge:ALEdgeLeft toEdge:ALEdgeRight ofView:photoImgV withOffset:5];
[nameL autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:80];
// content
UILabel *contentL=[UILabel new];
_contentL=contentL;
[contentL setFont:YTFONT_PF(13)];
[contentL setTextColor:YTColorFromRGB(0x414141)];
[bgImgV addSubview:contentL];
[contentL autoPinEdge:ALEdgeLeft toEdge:ALEdgeLeft ofView:nameL];
[contentL autoPinEdge:ALEdgeBottom toEdge:ALEdgeBottom ofView:photoImgV];
[contentL autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:36];
// time
UILabel *timeL=[UILabel new];
_timeL=timeL;
[timeL setFont:YTFONT_PF(15)];
[timeL setTextColor:YTColorFromRGB(0x5d5d5d)];
[bgImgV addSubview:timeL];
[timeL autoAlignAxis:ALAxisHorizontal toSameAxisOfView:nameL];
[timeL autoPinEdgeToSuperviewEdge:ALEdgeRight withInset:15];
//
UILabel *numL=[UILabel new];
_numL=numL;
[numL setTextAlignment:NSTextAlignmentCenter];
[numL setBackgroundColor:[UIColor redColor]];
[numL.layer setCornerRadius:15/2];
[numL.layer setMasksToBounds:true];
[numL setFont:YTFONT_PF(12)];
[numL setTextColor:YTColorFromRGB(0xffffff)];
[bgImgV addSubview:numL];
[numL autoPinEdge:ALEdgeRight toEdge:ALEdgeRight ofView:timeL];
[numL autoSetDimension:ALDimensionWidth toSize:15];
[numL autoSetDimension:ALDimensionHeight toSize:15];
[numL autoAlignAxis:ALAxisHorizontal toSameAxisOfView:contentL];
}
-(void)setConvM:(RCConversationModel *)convM withIsFirst:(BOOL)isF withUserInfo:(RCUserInfo *)info{
//
[_timeL setText:[YTConvertToTimeTool ConvertChatMessageTime:(convM.receivedTime>convM.sentTime?convM.receivedTime:convM.sentTime)/1000]];
[_numL setText:[NSString stringWithFormat:@"%ld",convM.unreadMessageCount]];
if(convM.unreadMessageCount==0){
[_numL setHidden:true];
}else{
[_numL setHidden:false];
}
if(isF){
//
NSString *titleStr=@"YOTO 官方 ";
NSMutableAttributedString *muT=[[NSMutableAttributedString alloc]initWithString:titleStr attributes:@{NSForegroundColorAttributeName:YTColorFromRGB(0x414141),NSFontAttributeName:YTFONT_PF_S(15)}];
[muT addAttributes:@{NSFontAttributeName:YTFONT_PF_S(12),NSForegroundColorAttributeName:[UIColor whiteColor],NSBackgroundColorAttributeName:YTMainColor} range:[titleStr rangeOfString:@" 官方 "]];
[_nameL setAttributedText:muT];
//
[_photoImgV setImage:[UIImage imageNamed:@"official"]];
if(convM.lastestMessage){
[_contentL setText:((RCTextMessage *)convM.lastestMessage).content];
}
}else if(info){
[_photoImgV sd_setImageWithURL:[NSURL URLWithString:info.portraitUri] placeholderImage:[UIImage imageNamed:@"userList"]];
[_nameL setText:info.name];
[_contentL setText:((RCTextMessage *)convM.lastestMessage).content];
}
}
@end
3.2 聊天页
- 聊天内容页 YTMessageViewController
YTMessageViewController.h
#import <RongIMKit/RongIMKit.h>
@interface YTMessageViewController : RCConversationViewController
@end
YTMessageViewController.m
@interface YTMessageViewController ()<RCIMUserInfoDataSource,YTCollectionHeadReusableViewProtocol,YTDZHeadCollectionReusableViewProtocol>
@property (nonatomic, strong) RCUserInfo *userInfo2;
@end
@implementation YTMessageViewController
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[IQKeyboardManager sharedManager].enable=false;
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[IQKeyboardManager sharedManager].enable=true;
}
//
- (void)viewDidLoad {
[super viewDidLoad];
/*
conversationMessageCollectionView是继承自UICollectionView
//
cell 为 RCMessageBaseCell,Model 为 RCMessageModel
// 会话类型
RCConversationType type=self.conversationType;
// targetId
NSString *targetId=self.targetId;
*/
// bgColor
[self.conversationMessageCollectionView setBackgroundColor:[UIColor whiteColor]];
// 头视图
if(_isCustome){
[self.conversationMessageCollectionView registerClass:[YTDZHeadCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"YTDZHeadCollectionReusableView"];
}else{
[self.conversationMessageCollectionView registerClass:[YTCollectionHeadReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"YTCollectionHeadReusableView"];
}
// (用来设置 管家是否与用户沟通)
if (!_isNoHave) {
[self message];
}
// 如果没有历史消息,发送初始消息
if(self.conversationDataRepository.count==0 && ![self.targetId isEqualToString:@"111111111111111"]){
//
[self sendInitMessage];
}
// 个人信息
[[RCIM sharedRCIM]setUserInfoDataSource:self];
// iconStyle 无效
// [[RCIM sharedRCIM]setGlobalConversationAvatarStyle:RC_USER_AVATAR_CYCLE];
}
// cell样式
-(void)willDisplayMessageCell:(RCMessageBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath{
//
if([cell isMemberOfClass:[RCTextMessageCell class]]){
// 内容
RCTextMessageCell *textC=(RCTextMessageCell *)cell;
UILabel *textL=(UILabel *)textC.textLabel;
[textL setTextColor:YTColorFromRGB(0x414141)];
[textL setFont:YTFONT_PF(15)];
// 头像切圆
UIImageView *portraitImageView = (UIImageView *)textC.portraitImageView;
portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
//
CGRect frame=portraitImageView.frame;
frame.origin.y=CGRectGetMaxY(textC.bubbleBackgroundView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
portraitImageView.frame=frame;
}else if([cell isMemberOfClass:[RCRichContentMessageCell class]]){
//
RCRichContentMessageCell *rCell=(RCRichContentMessageCell *)cell;
// 头像切圆
UIImageView *portraitImageView = (UIImageView *)rCell.portraitImageView;
portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
//
CGRect frame=portraitImageView.frame;
frame.origin.y=CGRectGetMaxY(rCell.bubbleBackgroundView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
portraitImageView.frame=frame;
}else if([cell isMemberOfClass:[RCVoiceMessageCell class]]){
RCVoiceMessageCell *rCell=(RCVoiceMessageCell *)cell;
// 头像切圆
UIImageView *portraitImageView = (UIImageView *)rCell.portraitImageView;
portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
//
CGRect frame=portraitImageView.frame;
frame.origin.y=CGRectGetMaxY(rCell.bubbleBackgroundView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
portraitImageView.frame=frame;
}else if([cell isMemberOfClass:[RCImageMessageCell class]]){
RCImageMessageCell *rCell=(RCImageMessageCell *)cell;
// 头像切圆
UIImageView *portraitImageView = (UIImageView *)rCell.portraitImageView;
portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
//
CGRect frame=portraitImageView.frame;
frame.origin.y=CGRectGetMaxY(rCell.pictureView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
portraitImageView.frame=frame;
}else if([cell isMemberOfClass:[RCLocationMessageCell class]]){
RCLocationMessageCell *rCell=(RCLocationMessageCell *)cell;
// 头像切圆
UIImageView *portraitImageView = (UIImageView *)rCell.portraitImageView;
portraitImageView.layer.cornerRadius = CGRectGetHeight(portraitImageView.frame)/2;
//
CGRect frame=portraitImageView.frame;
frame.origin.y=CGRectGetMaxY(rCell.pictureView.frame)-CGRectGetHeight(portraitImageView.frame)+6;
portraitImageView.frame=frame;
}
}
// 点击链接CELL时跳转
-(void)didTapUrlInMessageCell:(NSString *)url model:(RCMessageModel *)model{
/* 管家端
// id-是否定制
NSLog(@"%@",url);
NSArray *arr=[url componentsSeparatedByString:@"-"];
NSString *recId=arr[0];
BOOL isDZ=[arr[1] boolValue];
// 跳
*/
//
// 客户端跳
YTRouteDetailViewController *routeC=[YTRouteDetailViewController new];
if(_routeModel){
routeC.themeId=_routeModel.themeId;
}else{
NSArray *arr=[url componentsSeparatedByString:@"-"];
NSString *recId=arr[0];
routeC.themeId=recId;
}
[self.navigationController pushViewController:routeC animated:true];
}
// headSize
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section{
//
//
if(_isNoHave){
return CGSizeZero;
}else{
if(_isCustome){
return CGSizeMake(KScreenWidth, 229);
}else{
return CGSizeMake(KScreenWidth, 209);
}
}
}
// head foot
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
//
if(kind==UICollectionElementKindSectionHeader){
//
if(_isCustome){
YTDZHeadCollectionReusableView *view=[collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"YTDZHeadCollectionReusableView" forIndexPath:indexPath];
view.themeM=_routeModel;
view.dele=self;
view.tapG = ^{
YTRouteDetailViewController *routeC=[YTRouteDetailViewController new];
[routeC setThemeId:_routeModel.themeId];
[self.navigationController pushViewController:routeC animated:true];
};
return view;
}else{
YTCollectionHeadReusableView *view=[collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"YTCollectionHeadReusableView" forIndexPath:indexPath];
[view setThemeM:_routeModel];
view.dele=self;
view.tapG = ^{
YTRouteDetailViewController *routeC=[YTRouteDetailViewController new];
[routeC setThemeId:_routeModel.themeId];
[self.navigationController pushViewController:routeC animated:true];
};
return view;
}
}
return [UICollectionReusableView new];
}
#pragma mark dele
// 发送链接
-(void)goRoute{
//
RCRichContentMessage *msg=[RCRichContentMessage messageWithTitle:@"" digest:@"行程链接" imageURL:@"" url:[NSString stringWithFormat:@"%@-%@",_routeModel?_routeModel.themeId:self.targetId,[NSNumber numberWithBool:_isCustome]] extra:@""];
[[RCIM sharedRCIM]sendMessage:ConversationType_PRIVATE targetId:self.targetId content:msg pushContent:@"" pushData:@"" success:^(long messageId) {
//
NSLog(@"发送链接成功");
[self.conversationMessageCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.conversationDataRepository.count-1 inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:true];
} error:^(RCErrorCode nErrorCode, long messageId) {
[YTHUD showError:@"发送链接失败,请重新发送"];
}];
}
// moreHistory
-(void)moreHistory{
//
YTMoreHistoryMessageViewController *moreHistoryC=[YTMoreHistoryMessageViewController new];
moreHistoryC.themeId=_routeModel.themeId;
moreHistoryC.targetId=self.targetId;
moreHistoryC.conversationType = self.conversationType;
[self.navigationController pushViewController:moreHistoryC animated:true];
}
// 发送初始消息
-(void)sendInitMessage{
RCTextMessage *msg = [RCTextMessage messageWithContent:@"你好,看见你发的出行线路,感觉还不错,我挺感兴趣的,现在有时间可以具体聊聊吗?"];
[[RCIM sharedRCIM]sendMessage:ConversationType_PRIVATE targetId:self.targetId content:msg pushContent:@"" pushData:@"" success:^(long messageId) {
//
NSLog(@"发送初始消息成功");
} error:^(RCErrorCode nErrorCode, long messageId) {
[YTHUD showError:@"发送初始消息失败,请重新发送"];
}];
}
// (用来设置 管家是否与用户沟通)
-(void)message{
//
[AFNetWorkTool POST:YTBaseUrl(kCustomerMessagepush) params:@{@"Id":self.targetId,@"isChoosed":@"3"} success:^(NSURLSessionDataTask *task, id responseObject) {
if(SUCCESS){
NSLog(@"发送成功");
}
} fail:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"%@",error);
}];
}
#pragma mark 获取个人信息
- (void)getUserInfoWithUserId:(NSString *)userId completion:(void (^)(RCUserInfo *))completion{
YTAccount *acount=[YTAccountTool account];
if ([userId isEqualToString:[NSString stringWithFormat:@"%@",acount.userId]]) {
dispatch_async(dispatch_get_main_queue(), ^{
RCUserInfo *user = [[RCUserInfo alloc]init];
user.userId=[NSString stringWithFormat:@"%@",acount.userId];
user.name=acount.name;
user.portraitUri=acount.iconurl?[NSString stringWithFormat:@"%@%@",AccessPhoto,acount.iconurl]:@"";
completion(user);
});
}else{
[AFNetWorkTool POST:YTBaseUrl(kCustomerMessage) params:@{@"userId": userId} success:^(NSURLSessionDataTask *task, id responseObject) {
if(SUCCESS){
NSDictionary *dic=(NSDictionary *)responseObject[@"data"];
YTAccount *acount=[YTAccountTool account];
acount.userId=dic[@"id"];
acount.name=dic[@"name"];
acount.iconurl=YTImgBaseUrl(dic[@"photo"]);
//
dispatch_async(dispatch_get_main_queue(), ^{
RCUserInfo * userInfo2 = [[RCUserInfo alloc]init];
userInfo2.userId=acount.userId;
userInfo2.name=acount.name;
userInfo2.portraitUri=YTImgBaseUrl(acount.iconurl);
completion(userInfo2);
});
}
} fail:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"%@",error);
}];
}
}
@end
- 点击、长按相关 覆写方法
// --------- 点击、长按事件 ---------
// 点击CELL的 消息调用
-(void)didTapMessageCell:(RCMessageModel *)model{}
// 点击CELL的 URL调用
-(void)didTapUrlInMessageCell:(NSString *)url model:(RCMessageModel *)model{}
// 点击CELL的 phone调用
-(void)didTapPhoneNumberInMessageCell:(NSString *)phoneNumber model:(RCMessageModel *)model{}
// 点击CELL的 头像调用
-(void)didTapCellPortrait:(NSString *)userId{}
// 长按CELL的头像调用
-(void)didLongPressCellPortrait:(NSString *)userId{}
// 长按CELL的消息调用
-(void)didLongPressCellPortrait:(NSString *)userId{}
- 消息相关
// --------- 发送/删除/插入 消息(可覆写,要调super) ---------
// 发送消息
[self sendMessage:[RCMessageContent new] pushContent:@"接收方离线时显示的远程推送内容"];
// 重新发送消息
[self resendMessage:[RCMessageContent new]];
// 插入消息(临时,退出再进就没了)
[self appendAndDisplayMessage:[RCMessage new]];
// 发送多媒体信息(除文本消息外都是)
[self sendMediaMessage:[RCMessageContent new] pushContent:@"" appUpload:<#(BOOL)#>];
// 删除消息
[self deleteMessage:[RCMessageModel new]];
举例:插入
// 是否保存到本地数据库,如果不保存,则下次进入聊天界面将不再显示。
BOOL saveToDB = NO;
RCMessage *insertMessage;
RCInformationNotificationMessage *warningMessage = [RCInformationNotificationMessage notificationWithMessage:@"提醒消息" extra:nil];
if (saveToDB) {
// 如果保存到本地数据库,需要调用insertMessage生成消息实体并插入数据库。
insertMessage = [[RCIMClient sharedRCIMClient] insertOutgoingMessage:self.conversationType
targetId:self.targetId
sentStatus:SentStatus_SENT
content:warningMessage];
} else {
// 如果不保存到本地数据库,需要初始化消息实体并将messageId要设置为-1。
insertMessage =[[RCMessage alloc] initWithType:self.conversationType
targetId:self.targetId
direction:MessageDirection_SEND
messageId:-1
content:warningMessage];
}
// 在当前聊天界面插入该消息
[self appendAndDisplayMessage:insertMessage];
// --------- 消息发送前后、显示CELL前的回调 (覆写做额外处理) ---------
// 发送消息前调用
-(RCMessageContent *)willSendMessage:(RCMessageContent *)messageContent{ return messageContent; }
// 发送消息后调用
-(void)didSendMessage:(NSInteger)status content:(RCMessageContent *)messageContent{}
// 插入消息前调用
-(RCMessage *)willAppendAndDisplayMessage:(RCMessage *)message{ return message; }
// 显示cell前调用
-(void)willDisplayMessageCell:(RCMessageBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath{}
// --------- 未读消息数UI相关 ---------
// 导航栏左侧按钮 未读消息数(置为nil,不再显示)
// 需要统计未读数的会话类型数组
self.displayConversationTypeArray=nil;
// 当未读消息超过一个屏幕时 在右上角显示未读消息数 默认:false不显示
[self setEnableUnreadMessageIcon:true];
// 当未处在最下方,收到消息时 右下角是否显示按钮-滚动到最下方 默认:false不显示
[self setEnableNewComingMessageIcon:true];
- 输入工具栏
// 输入框的默认输入模式
[self setDefaultInputType:RCChatSessionInputBarInputText];
/*
RCChatSessionInputBarInputText, 文本(默认)
RCChatSessionInputBarInputVoice, 语音
RCChatSessionInputBarInputExtention 扩展
*/
// 输入框的UI样式
// 按照会话类型来设置的,不要随意设置
[self.chatSessionInputBarControl setInputBarType:RCChatSessionInputBarControlDefaultType style:RC_CHAT_INPUT_BAR_STYLE_CONTAINER];
/*
type
RCChatSessionInputBarControlDefaultType 非公众服务(默认)
RCChatSessionInputBarControlPubType 公众服务
RCChatSessionInputBarControlCSRobotType 客服机器人会话
RCChatSessionInputBarControlNoAvailableType 客服机器人会话
style
RC_CHAT_INPUT_BAR_STYLE_SWITCH_CONTAINER_EXTENTION 语音/文本切换功能+内容输入功能+扩展功能
RC_CHAT_INPUT_BAR_STYLE_EXTENTION_CONTAINER_SWITCH 扩展功能+内容输入功能+语音/文本切换功能
RC_CHAT_INPUT_BAR_STYLE_CONTAINER_SWITCH_EXTENTION 内容输入功能+语音/文本切换功能+扩展功能
RC_CHAT_INPUT_BAR_STYLE_CONTAINER_EXTENTION_SWITCH 内容输入功能+扩展功能+语音/文本切换功能
RC_CHAT_INPUT_BAR_STYLE_SWITCH_CONTAINER 语音/文本切换功能+内容输入功能
RC_CHAT_INPUT_BAR_STYLE_CONTAINER_SWITCH 内容输入功能+语音/文本切换功能
RC_CHAT_INPUT_BAR_STYLE_EXTENTION_CONTAINER 扩展功能+内容输入功能
RC_CHAT_INPUT_BAR_STYLE_CONTAINER_EXTENTION 内容输入功能+扩展功能
RC_CHAT_INPUT_BAR_STYLE_CONTAINER 内容输入功能
*/
// 自定义工具栏
[self setChatSessionInputBarControl:[RCChatSessionInputBarControl new]];
- Emoji表情区域
Emoji表情,可修改plist文件
// 自定义EmojiView
[self.chatSessionInputBarControl setEmojiBoardView:[RCEmojiBoardView new]];
- 扩展区域
// 扩展View是否隐藏
[self.extensionView setHidden:true];
// 添加item (tag不要1001~,那是系统预留的)
[self.chatSessionInputBarControl.pluginBoardView insertItemWithImage:[UIImage imageWithNamed:@""] title:@"" tag:10];
[self.chatSessionInputBarControl.pluginBoardView insertItemWithImage:[UIImage imageWithNamed:@""] title:@"" atIndex:0 tag:10];
// 更新item
[self.chatSessionInputBarControl.pluginBoardView updateItemAtIndex:0 image:[UIImage imageWithNamed:@""] title:@""];
[self.chatSessionInputBarControl.pluginBoardView updateItemWithTag:10 image:[UIImage imageWithNamed:@""] title:@""];
// 移除item
[self.chatSessionInputBarControl.pluginBoardView removeItemAtIndex:0];
[self.chatSessionInputBarControl.pluginBoardView removeItemWithTag:10];
[self.chatSessionInputBarControl.pluginBoardView removeAllItems];
// 覆写,点击某项后调用
- (void)pluginBoardView:(RCPluginBoardView *)pluginBoardView clickedItemWithTag:(NSInteger)tag {
[super pluginBoardView:pluginBoardView clickedItemWithTag:tag];
switch (tag) {
case 2001:
[self navigateToPay];
break;
case 2002:
[self navigateToPic];
break;
case 2003:
[self navigateToSend];
break;
default:
break;
}
}
3.3 聊天页(自定义消息和CELL)
第一步:
自定义消息,并注册(registerMessageType)
第二步:
自定义cell ,并注册(registerClass:forMessageClass:,则不再走 cellForItem、 sizeForItem)
第三步:
发送自定义消息
第一步:
自定义消息,并注册(registerMessageType)
// 创建自定义消息类
继承:RCMessageContent (所有消息的基类),并实现其遵守的协议(必须实现,见下)
// 注册自定义消息类(必须注册)
[[RCIM sharedRCIM]registerMessageType:[RCMessageContent class]];
:RCMessageContent
该类遵守了RCMessageCoding,RCMessagePersistentCompatible,RCMessageContentView3个协议,如下
// ---------- 消息内容的编解码协议 ----------
@protocol RCMessageCoding <NSObject>
@required
// 返回消息唯一标识(不要以RC:开头)
+(NSString *)getObjectName;
// 消息内容->json
- (NSData *)encode;
// json->消息内容
- (void)decodeWithData:(NSData *)data;
/*
主要有三个功能:
提供消息唯一标识符
消息发送时将消息中的所有信息编码为 JSON 数据传输
消息接收时将 JSON 数据解码还原为消息对象
*/
@end
// ---------- 消息内容的存储协议 ----------
@protocol RCMessagePersistentCompatible <NSObject>
@required
// 返回消息的存储策略(在本地是否存储、是否计入未读消息数)
+(RCMessagePersistent)persistentFlag;
/*
用于确定消息内容的存储策略,指明此消息类型在本地是否存储、是否计入未读消息数,RCMessagePersistent有4种:
MessagePersistent_NONE 在本地不存储,不计入未读数。
MessagePersistent_ISPERSISTED 表示客户端收到消息后,要进行未读消息计数(未读消息数增加 1),所有内容型消息都应该设置此值。非内容类消息暂不支持消息计数。
MessagePersistent_ISCOUNTED 表示客户端收到消息后,要进行存储,并在之后可以通过接口查询。
MessagePersistent_STATUS 在本地不存储,不计入未读数,并且如果对方不在线,服务器会直接丢弃该消息,对方如果之后再上线也不会再收到此消息(聊天室类型除外,此类消息聊天室会视为普通消息)。
*/
@end
// ---------- 消息内容摘要的协议 ----------
@protocol RCMessageContentView
@optional
// 返回在会话列表、本地通知中显示的消息内容摘要(最新消息的摘要)
-(NSString *)conversationDigest;
@end
实例
#define RCDeliverSentMessageTypeIdentifier @"RCC:XDispatchMsg"
#import <RongIMLib/RongIMLib.h>
#import "ChatModel.h"
@interface RCDeliverSentMessage : RCMessageContent
@property (nonatomic, strong) ChatModel *chatModel;
@end
#import "RCDeliverSentMessage.h"
#import "MJExtension.h"
@implementation RCDeliverSentMessage
#pragma mark RCMessagePersistentCompatible协议
///消息是否存储,是否计入未读数
+ (RCMessagePersistent)persistentFlag {
return (MessagePersistent_ISPERSISTED | MessagePersistent_ISCOUNTED);
}
#pragma mark NSCoding协议
/// NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.chatModel = [aDecoder decodeObjectForKey:@"chatModel"];
}
return self;
}
/// NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.chatModel forKey:@"chatModel"];
}
#pragma mark RCMessageContentView协议
/// 会话列表中显示的最新消息内容
- (NSString *)conversationDigest {
return @"[已发货]";
}
#pragma mark RCMessageCoding协议
///将消息内容编码成json
- (NSData *)encode {
NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];
if (self.chatModel) {
[dataDict setObject:self.chatModel.mj_keyValues forKey:@"chatModel"];
}
if (self.senderUserInfo) {
NSMutableDictionary *userInfoDic = [[NSMutableDictionary alloc] init];
if (self.senderUserInfo.name) {
[userInfoDic setObject:self.senderUserInfo.name forKeyedSubscript:@"name"];
}
if (self.senderUserInfo.portraitUri) {
[userInfoDic setObject:self.senderUserInfo.portraitUri forKeyedSubscript:@"portrait"];
}
if (self.senderUserInfo.userId) {
[userInfoDic setObject:self.senderUserInfo.userId forKeyedSubscript:@"id"];
}
[dataDict setObject:userInfoDic forKey:@"user"];
}
NSData *data = [NSJSONSerialization dataWithJSONObject:dataDict options:kNilOptions error:nil];
return data;
}
///将json解码生成消息内容
- (void)decodeWithData:(NSData *)data {
if (data) {
__autoreleasing NSError *error = nil;
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (dictionary) {
self.chatModel = [ChatModel mj_objectWithKeyValues:dictionary[@"chatModel"]];
NSDictionary *userinfoDic = dictionary[@"user"];
[self decodeUserInfo:userinfoDic];
}
}
}
///消息的类型名
+ (NSString *)getObjectName {
return DeliverSentMessageTypeIdentifier;
}
-(instancetype)init{
if (self = [super init]) {
self.chatModel = [[ChatModel alloc]init];
}
return self;
}
@end
第二步:
自定义cell ,并注册(registerClass:forMessageClass:,则不再走 cellForItem、 sizeForItem)
![](https://img.haomeiwen.com/i5111884/10cb48b2ed6183ea.jpg)
:RCMessageBaseCell
在baseContentView添加自定义控件(建议在 baseContentView 上方预留 10)
适合高度自定制
或
:RCMessageCell
在继承 RCMessageBaseCell 的基础上增加显示头像和昵称
在messageContentView添加自定义控件。
不适合高度自定制
// 注册CELL
[self registerClass:[RCMessageCell class] forMessageClass:[RCMessageContent class]];
// size cell中实现(必须实现,不会再走cellforItem、sizeForItem)
+(CGSize)sizeForMessageModel:(RCMessageModel *)model withCollectionViewWidth:(CGFloat)collectionViewWidth referenceExtraHeight:(CGFloat)extraHeight{
// ExtraHeigh: cell内容区域之外的高度
return CGSizeMake(100, 100);
}
4. 相关
- 用户信息
为了信息安全和一致:存储在App服务器而不是融云服务器。
融云提供了 用户信息提供者、群组信息提供者、群名片信息提供者,只需实现响应协议即可正确获取并显示信息。
// --------- 群组信息提供者 ---------
<RCIMGroupInfoDataSource>
[[RCIM sharedRCIM]setGroupInfoDataSource:self];
- (void)getGroupInfoWithGroupId:(NSString *)groupId
completion:(void (^)(RCGroup *groupInfo))completion{
}
// --------- 群名片信息提供者 ---------
<RCIMGroupUserInfoDataSource>
[[RCIM sharedRCIM]setGroupUserInfoDataSource:self];
- (void)getUserInfoWithUserId:(NSString *)userId
inGroup:(NSString *)groupId
completion:(void (^)(RCUserInfo *userInfo))completion{
}
// --------- 用户信息提供者 ---------
<RCIMUserInfoDataSource>
[[RCIM sharedRCIM]setUserInfoDataSource:self];
// 融云在需要显示用户信息时调用
- (void)getUserInfoWithUserId:(NSString *)userId completion:(void (^)(RCUserInfo *))completion{
// 判断是否是自己
YTAccount *acount=[YTAccountTool account];
if ([userId isEqualToString:[NSString stringWithFormat:@"%@",acount.userId]]) { // 是
RCUserInfo *user = [[RCUserInfo alloc]init];
user.userId=[NSString stringWithFormat:@"%@",acount.userId];
user.name=acount.name;
user.portraitUri=acount.iconurl?[NSString stringWithFormat:@"%@%@",AccessPhoto,acount.iconurl]:@"";
return completion(user);
}else{ // 别人
// 获取信息
[AFNetWorkTool POST:YTBaseUrl(kCustomerMessage) params:@{@"userId":userId} success:^(NSURLSessionDataTask *task, id responseObject) {
if(SUCCESS){
NSDictionary *dic=(NSDictionary *)responseObject[@"data"];
//
RCUserInfo *userInfo2 = [[RCUserInfo alloc]init];
userInfo2.userId=dic[@"id"];
userInfo2.name=dic[@"name"];
userInfo2.portraitUri=YTImgBaseUrl(dic[@"photo"]);
// 刷新UI
[[RCIM sharedRCIM]refreshUserInfoCache:userInfo2 withUserId:userInfo2.userId];
}
} fail:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"%@",error);
}];
}
return completion(nil);
}
缓存
缓存---有:显示;无:通过信息提供者获取信息并缓存。
// 是否持久化缓存到本地(默认:false,通过信息提供者获取数据后进行缓存,关闭App后删除缓存;true则不会删除,重新启动后使用缓存显示)
[[RCIM sharedRCIM]setEnablePersistentUserInfoCache:true];
// 清除缓存数据
// 清除用户信息缓存
[[RCIM sharedRCIM]clearUserInfoCache];
// 清除群组信息缓存
[[RCIM sharedRCIM]clearGroupInfoCache];
// 清除群名片信息缓存
[[RCIM sharedRCIM]clearGroupUserInfoCache];
// 获取缓存
// 获取缓存中的用户信息
[[RCIM sharedRCIM]getUserInfoCache:@"userId"];
// 获取缓存中的群组信息
[[RCIM sharedRCIM]getGroupInfoCache:@"groupId"];
// 获取缓存中的群名片信息
[[RCIM sharedRCIM]getGroupUserInfoCache:@"userId" withGroupId:@"groupId"];
// 刷新缓存,可能不会立即刷新UI,可通过reloadData强制刷新
// 刷新用户缓存
[[RCIM sharedRCIM]refreshUserInfoCache:[RCUserInfo new] withUserId:@""];
// 刷新群组缓存
[[RCIM sharedRCIM]refreshGroupInfoCache:[RCGroup new] withGroupId:@""];
// 刷新群中的个人名片缓存
[[RCIM sharedRCIM]refreshGroupUserInfoCache:[RCUserInfo new] withUserId:@"" withGroupId:@""];
- 会话类型
单聊 ConversationType_PRIVATE
// 一个继承了RCConversationViewController的类
YTMessageViewController *conversationVC = [YTMessageViewController new];
conversationVC.conversationType = ConversationType_PRIVATE; // 会话类型
conversationVC.title = ((RCUserInfo *)model.extend).name; // navTitle
conversationVC.targetId = model.targetId; // 目标ID
[self.navigationController pushViewController:conversationVC animated:YES];
群组 ConversationType_GROUP
// 进入群组:和单聊相同,只是type不同
所有群组操作都通过App后台交互,再由App后台和融云交互。
讨论组 ConversationType_DISCUSSION
两个以上用户一起进行聊天时生成讨论组。
用户可以自行添加好友生成一个讨论组聊天。
成员关系由融云负责建立并保持。
退出聊天界面或者离线后可以收到推送通知。
同一个用户最多可加入 500 个讨论组。
讨论组不能解散。
// 进入讨论组:和单聊相同,只是type不同
// 创建讨论组(用户ID)
[[RCIM sharedRCIM]createDiscussion:@"讨论组名称" userIdList:@[] success:^(RCDiscussion *discussion) {
// discussion讨论组对象
} error:^(RCErrorCode status) {
NSLog(@"创建讨论组出错,错误码:%d",(int)status);
}];
// 添加用户到讨论组(用户ID)
[[RCIM sharedRCIM]addMemberToDiscussion:@"讨论组id" userIdList:@[] success:^(RCDiscussion *discussion) {
// discussion讨论组对象
} error:^(RCErrorCode status) {
NSLog(@"加入讨论组出错,错误码:%d",(int)status);
}];
// 移除成员从讨论组
[[RCIM sharedRCIM]removeMemberFromDiscussion:@"讨论组id" userId:@"用户id" success:^(RCDiscussion *discussion) {
// discussion讨论组对象
} error:^(RCErrorCode status) {
NSLog(@"移除成员从讨论组出错,错误码:%d",(int)status);
}];
// 退出讨论组
[[RCIM sharedRCIM]quitDiscussion:@"讨论组id" success:^(RCDiscussion *discussion) {
// discussion讨论组对象
} error:^(RCErrorCode status) {
NSLog(@"退出讨论组出错,错误码:%d",(int)status);
}];
// 获取讨论组信息出错
[[RCIM sharedRCIM]getDiscussion:@"讨论组id" success:^(RCDiscussion *discussion) {
// discussion讨论组对象
} error:^(RCErrorCode status) {
NSLog(@"获取讨论组信息出错,错误码:%d",(int)status);
}];
// 设置讨论组名称
[[RCIM sharedRCIM]setDiscussionName:@"讨论组id" name:@"讨论组名称" success:^{
// discussion讨论组对象
} error:^(RCErrorCode status) {
NSLog(@"设置讨论组名称出错,错误码:%d",(int)status);
}];
// 设置讨论组加人权限(是否放开)
[[RCIM sharedRCIM]setDiscussionInviteStatus:@"讨论组id" isOpen:true success:^{
// discussion讨论组对象
} error:^(RCErrorCode status) {
NSLog(@"设置讨论组加人权限出错,错误码:%d",(int)status);
}];
聊天室 ConversationType_CHATROOM
// 加入聊天室(设置id,type,直接push就加入了。同单聊)
聊天室的消息没有 Push 通知,没有人数限制,退出后不再接收消息并清空本地消息记录。
融云默认一个用户同时只能加入一个聊天室。
一个聊天室一个小时内没有人发送消息或者加入时会被自动销毁。
// 设置 获取历史记录的条数(默认获取10条历史记录)(-1:不获取,0:默认10条,0<count<=50)
[self setDefaultHistoryMessageCountOfChatRoom:-1];
// 自定义
// 加入聊天室(不存在则创建)
[[RCIMClient sharedRCIMClient]joinChatRoom:@"聊天室ID" messageCount:10 success:^{
} error:^(RCErrorCode status) {
NSLog(@"加入聊天室出错,错误码:%d",(int)status);
}];
// 加入已存在的聊天室
[[RCIMClient sharedRCIMClient]joinExistChatRoom:@"聊天室ID" messageCount:10 success:^{
} error:^(RCErrorCode status) {
NSLog(@"加入聊天室出错,错误码:%d",(int)status);
}];
// 退出聊天室
[[RCIMClient sharedRCIMClient]quitChatRoom:@"聊天室ID" success:^{
} error:^(RCErrorCode status) {
NSLog(@"退出聊天室出错,错误码:%d",(int)status);
}];
// 获取聊天室信息
[[RCIMClient sharedRCIMClient]getChatRoomInfo:@"聊天室ID" count:10 order:RC_ChatRoom_Member_Desc success:^(RCChatRoomInfo *chatRoomInfo) {
} error:^(RCErrorCode status) {
NSLog(@"获取聊天室信息出错,错误码:%d",(int)status);
}];
系统会话 ConversationType_SYSTEM
不能从App发起系统会话,只能通过App服务器来发起
客服 ConversationType_CUSTOMERSERVICE
客服---收费 (融云Demo有个RCDCustomerServiceViewController)
// 启动客服
// type,title,客服id,csInfo(用于上传用户信息到客服后台,数据的nickName和portraitUrl必须填写),push即可
// 退出客服
// 点击返回按钮即可退出(默认),可通过改 来让退出页面不退出客服
<key>CustomerService</key>
<dict>
<key>SuspendWhenLeave</key>
<true/> 改为false
</dict>
// 评价
// 在客服页面停留一分钟后退出会自动弹出客服评价,退出时弹出(人工和机器 界面不一样)
公众号
// 跳转到 已关注的公众号列表页
RCPublicServiceListViewController *publicServiceVC = [[RCPublicServiceListViewController alloc] init];
[self.navigationController pushViewController:publicServiceVC animated:YES];
// 跳转到 搜索公众号页
RCPublicServiceSearchViewController *searchFirendVC = [[RCPublicServiceSearchViewController alloc] init];
[self.navigationController pushViewController:searchFirendVC animated:YES];
// 跳转到会话页(type,id,navTitle,userName)
- 消息类型
- 系统自带消息类型
文本消息
RCTextMessage *txtMsg = [RCTextMessage messageWithContent:@""];
图文消息
RCRichContentMessage *richMsg = [RCRichContentMessage messageWithTitle:@"title" digest:@"内容摘要" imageURL:@"imgURL" url:@"跳转url" extra:nil];
位置消息 (缩略图、坐标、地址名)
RCLocationMessage *locationMessage = [RCLocationMessage messageWithLocationImage:[UIImage new] location:location locationName:@"locationName"];
语音消息
RCVoiceMessage *rcVoiceMessage = [RCVoiceMessage messageWithAudio:[NSData new] duration:10];
/*
必须:wav格式、采样率必须是8000Hz,采样位数(精度)必须为16位,10s
参考IMKit中的录音参数:
NSDictionary *settings = @{AVFormatIDKey: @(kAudioFormatLinearPCM),
AVSampleRateKey: @8000.00f,
AVNumberOfChannelsKey: @1,
AVLinearPCMBitDepthKey: @16,
AVLinearPCMIsNonInterleaved: @NO,
AVLinearPCMIsFloatKey: @NO,
AVLinearPC'MIsBigEndianKey: @NO};
*/
发送上述消息(会话类型,目标用户ID,文本消息,接收方离线时需要显示的内容,接收方离线时需要在远程推送中不显示的内容)
[[RCIM sharedRCIM]sendMessage:ConversationType_PRIVATE targetId:@"目标ID" content:txtMsg pushContent:@"" pushData:@"" success:^(long messageId) {
} error:^(RCErrorCode nErrorCode, long messageId) {
}];
图片消息 (缩略图:240*240,大图尺寸:960*960)
RCImageMessage *imageMessage = [RCImageMessage messageWithImage:[UIImage new]];
RCImageMessage *imageMessage = [RCImageMessage messageWithImageURI:@"imgURL"];
RCImageMessage *imageMessage = [RCImageMessage messageWithImageData:[NSData data]];
/*
缩略图尺寸为:240 x 240 像素,以宽度和高度中较长的边不超过 240 像素等比压缩。
大图尺寸为:960 x 960 像素,以宽度和高度中较长的边不超过 960 像素等比压缩
*/
文件消息
RCFileMessage *fileMessage = [RCFileMessage messageWithFile:@"filePath"];
发送上述消息,存储有效期为 6 个月
[[RCIM sharedRCIM]sendMediaMessage:ConversationType_PRIVATE targetId:@"目标ID" content: imageMessage pushContent:@"" pushData:@"" progress:^(int progress, long messageId) { // 0~100
} success:^(long messageId) {
} error:^(RCErrorCode errorCode, long messageId) {
} cancel:^(long messageId) {
}];
- 消息接收监听
// 消息接收监听 <RCIMReceiveMessageDelegate> 放在AppDelegate中
[RCIM sharedRCIM].receiveMessageDelegate=self;
// 收到消息时调用(此时message已被存储数据库,详见下图)
-(void)onRCIMReceiveMessage:(RCMessage *)message left:(int)left{
// 发送通知、播放音效、震动。。。
// AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); // 震动
}
// 撤回消息时回调
-(void)onRCIMMessageRecalled:(long)messageId{
}
// 接收到消息准备播放方提示音前调用(默认:false,播放默认提示音)
-(BOOL)onRCIMCustomAlertSound:(RCMessage *)message{
return true; // 不播放默认提示音
}
// 应用处于后台时,接收到消息时弹出本地通知时调用(默认:false,默认提示框)
-(BOOL)onRCIMCustomLocalNotification:(RCMessage *)message withSenderName:(NSString *)senderName{
// true则不弹通知
return false;
}
![](https://img.haomeiwen.com/i5111884/039f4b8d61e37b1e.png)
- 消息提醒
// 设置 是否屏蔽消息提醒
[[RCIMClient sharedRCIMClient]setConversationNotificationStatus:ConversationType_PRIVATE targetId:@"id" isBlocked:true success:^(RCConversationNotificationStatus nStatus) {
} error:^(RCErrorCode status) {
NSLog(@"屏蔽消息提醒出错,错误码:%d",(int)status);
}];
// 获取 是否屏蔽消息提醒
[[RCIMClient sharedRCIMClient]getConversationNotificationStatus:ConversationType_PRIVATE targetId:@"" success:^(RCConversationNotificationStatus nStatus) {
} error:^(RCErrorCode status) {
NSLog(@"获取是否屏蔽消息提醒出错,错误码:%d",(int)status);
}];
时间段屏蔽
// 设置 时间段屏蔽,(开始屏蔽消息提醒的时间,持续时间0~1440min)
[[RCIMClient sharedRCIMClient]setNotificationQuietHours:@"HH:MM:SS s" spanMins:10 success:^{
} error:^(RCErrorCode status) {
NSLog(@"时间段屏蔽出错,错误码:%d",(int)status);
}];
// 删除 时间段屏蔽
[[RCIMClient sharedRCIMClient]removeNotificationQuietHours:^{
} error:^(RCErrorCode status) {
NSLog(@"删除时间段屏蔽出错,错误码:%d",(int)status);
}];
// 获取 时间段屏蔽
[[RCIMClient sharedRCIMClient]getNotificationQuietHours:^(NSString *startTime, int spansMin){
} error:^(RCErrorCode status) {
NSLog(@"获取时间段屏蔽出错,错误码:%d",(int)status);
}];
通知
可通过RCIMReceiveMessageDelegate的onRCIMCustomAlertSound、onRCIMCustomLocalNotification来选择性关闭
// 是否关闭提示音(默认:false)
[[RCIM sharedRCIM]setDisableMessageAlertSound:true];
// 是否关闭通知
[[RCIM sharedRCIM]setDisableMessageNotificaiton:true];
- 小角标
获取所有未读消息数
#import <RongIMKit/RongIMKit.h>
// 获取所有未读消息数
-(NSInteger)getUnreadCount{
int unreadMsgCount = [[RCIMClient sharedRCIMClient] getUnreadCount:@[
@(ConversationType_PRIVATE),
@(ConversationType_DISCUSSION),
@(ConversationType_APPSERVICE),
@(ConversationType_PUBLICSERVICE),
@(ConversationType_GROUP)
]];
return unreadMsgCount ;
}
// 设置角标
-(void)setBadageNum{
NSInteger unreadMessageCount = [self getUnreadCount];
// 设置tabbar 的icon
UITabBarController *tabbar = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController ;
if ([tabbar isKindOfClass:[UITabBarController class]]) {
UITabBarItem *item = [tabbar.tabBar.items objectAtIndex:1];
// 如果没有未读消息返回值为nil
if (unreadMessageCount == 0 || unreadMessageCount == nil) {
item.badgeValue = nil ;
return ;
}
item.badgeValue = [NSString stringWithFormat:@"%d",unreadMessageCount];
}
}
收到消息时设置小角标
// <RCIMReceiveMessageDelegate>
[RCIM sharedRCIM].receiveMessageDelegate=self;
// 收到消息时调用(更新)
-(void)onRCIMReceiveMessage:(RCMessage *)message left:(int)left{
dispatch_async(dispatch_get_main_queue(), ^{
[self setBadageNum];
});
}
进入聊天页设置小角标
功能进阶部分(红包、动态表情、群组@) 待续