iOS开发之笔记摘录

融云聊天之iOS笔记摘录

2018-04-21  本文已影响0人  平安喜乐698
目录
    1. 概念
    2. 集成融云
    3. 会话列表页(自定义CELL)
       聊天页(自定义消息、CELL)
    4. 用户信息、小角标、消息类型

1. 概念

融云SDK的系统架构

IMKit
  封装了各种界面对象。
  用于快速集成。
IMLib
  封装了通信能力和 Conversation,Message 等各种对象,提供基本通信能力库。
  用于高度自定义UI。
Protocol
  融云的核心协议栈,使用融云自定义的私有二进制协议
融云SDK的系统架构

相关名词

通知
  提示用户的方式,消息、角标、弹窗
推送
  从服务器发送消息到前端的技术
广播
  向所有客户端发送一条消息
应用切换到后台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
  1. 自定义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 聊天页

  1. 聊天内容页 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
  1. 点击、长按相关 覆写方法
// --------- 点击、长按事件 ---------

// 点击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{}
  1. 消息相关
// --------- 发送/删除/插入 消息(可覆写,要调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];
  1. 输入工具栏
    // 输入框的默认输入模式
    [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]];
  1. Emoji表情区域
    Emoji表情,可修改plist文件
    // 自定义EmojiView
    [self.chatSessionInputBarControl setEmojiBoardView:[RCEmojiBoardView new]];
  1. 扩展区域
    // 扩展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)

QQ20171113-114916@2x.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. 相关
  1. 用户信息
  为了信息安全和一致:存储在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:@""];
  1. 会话类型

单聊 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)
  1. 消息类型
  1. 系统自带消息类型
文本消息
    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) {
    }];
  1. 消息接收监听
    // 消息接收监听 <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;
}
  1. 消息提醒
    // 设置 是否屏蔽消息提醒
    [[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];
  1. 小角标
获取所有未读消息数

#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];
        });
    }
进入聊天页设置小角标

功能进阶部分(红包、动态表情、群组@) 待续

上一篇 下一篇

猜你喜欢

热点阅读