融云聊天之iOS筆記摘錄

目錄
    1. 概念
    2. 集成融云
    3. 會話列表頁(自定義CELL)
       聊天頁(自定義消息痒留、CELL)
    4. 用戶信息谴麦、小角標、消息類型

1. 概念

融云SDK的系統(tǒng)架構

IMKit
  封裝了各種界面對象伸头。
  用于快速集成匾效。
IMLib
  封裝了通信能力和 Conversation,Message 等各種對象恤磷,提供基本通信能力庫面哼。
  用于高度自定義UI野宜。
Protocol
  融云的核心協(xié)議棧,使用融云自定義的私有二進制協(xié)議
融云SDK的系統(tǒng)架構

相關名詞

通知
  提示用戶的方式魔策,消息匈子、角標、彈窗
推送
  從服務器發(fā)送消息到前端的技術
廣播
  向所有客戶端發(fā)送一條消息
應用切換到后臺2分鐘后闯袒,將自動切斷與服務器的連接旬牲,變?yōu)殡x線狀態(tài)。

用戶A向用戶B發(fā)送消息:
  首先搁吓,應用會將消息傳遞給融云服務器原茅,如果用戶B在線則將消息傳給用戶B,如果用戶B不在線堕仔,則將推送消息給用戶B綁定的設備擂橘。
2. 集成融云
第一步:
    蘋果開發(fā)者中心創(chuàng)建Identify,打開推送功能摩骨,創(chuàng)建測試通贞、發(fā)布推送證書,下載雙擊安裝并導出為p12格式恼五。
    融云開發(fā)者官網創(chuàng)建應用昌罩,上傳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失敗羡滑,不用再次調用(避免循環(huán)調用)
                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          系統(tǒng)會話(只能由服務器發(fā)起)
            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:@"您關閉了系統(tǒng)通知" withContent:@"開啟系統(tǒng)通知淳梦,以免錯過重要消息" withImgName:@"orderG" withButtonTitle:@"去開啟"];
            recV.goBlock=^{
                
                // 跳手機系統(tǒng)的 通知設置
                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
            };
            
        }else{
            NSLog(@"推送打開 8.0");
        }
    }else{
        //
        UIRemoteNotificationType type = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
        if(UIRemoteNotificationTypeNone == type){
            NSLog(@"推送關閉");
            YTRecommendView *recV=[YTRecommendView ViewWithTitle:@"您關閉了系統(tǒng)通知" withContent:@"開啟系統(tǒng)通知析砸,以免錯過重要消息" withImgName:@"orderG" withButtonTitle:@"去開啟"];
            recV.goBlock=^{
                
                // 跳手機系統(tǒng)的 通知設置
                [[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];
    }

    // 如果沒有歷史消息弦疮,發(fā)送初始消息
    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
// 發(fā)送鏈接
-(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(@"發(fā)送鏈接成功");
        [self.conversationMessageCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:self.conversationDataRepository.count-1 inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:true];
    } error:^(RCErrorCode nErrorCode, long messageId) {
        [YTHUD showError:@"發(fā)送鏈接失敗,請重新發(fā)送"];
    }];
}
// 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];
}
// 發(fā)送初始消息
-(void)sendInitMessage{
    
    RCTextMessage *msg = [RCTextMessage messageWithContent:@"你好蜘醋,看見你發(fā)的出行線路胁塞,感覺還不錯,我挺感興趣的压语,現在有時間可以具體聊聊嗎啸罢?"];
    [[RCIM sharedRCIM]sendMessage:ConversationType_PRIVATE targetId:self.targetId content:msg pushContent:@"" pushData:@"" success:^(long messageId) {
        //
        NSLog(@"發(fā)送初始消息成功");
    } error:^(RCErrorCode nErrorCode, long messageId) {
        [YTHUD showError:@"發(fā)送初始消息失敗,請重新發(fā)送"];
    }];
}
// (用來設置 管家是否與用戶溝通)
-(void)message{
    //
    [AFNetWorkTool POST:YTBaseUrl(kCustomerMessagepush) params:@{@"Id":self.targetId,@"isChoosed":@"3"} success:^(NSURLSessionDataTask *task, id responseObject) {
        if(SUCCESS){
            NSLog(@"發(fā)送成功");
        }
    } 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. 消息相關
// --------- 發(fā)送/刪除/插入 消息(可覆寫,要調super) --------- 

    // 發(fā)送消息
    [self sendMessage:[RCMessageContent new] pushContent:@"接收方離線時顯示的遠程推送內容"];
    // 重新發(fā)送消息
    [self resendMessage:[RCMessageContent new]];
    // 插入消息(臨時厕怜,退出再進就沒了)
    [self appendAndDisplayMessage:[RCMessage new]];
    // 發(fā)送多媒體信息(除文本消息外都是)
    [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];
// --------- 消息發(fā)送前后置侍、顯示CELL前的回調 (覆寫做額外處理) ---------

// 發(fā)送消息前調用
-(RCMessageContent *)willSendMessage:(RCMessageContent *)messageContent{    return messageContent; }
// 發(fā)送消息后調用 
-(void)didSendMessage:(NSInteger)status content:(RCMessageContent *)messageContent{}
// 插入消息前調用 
-(RCMessage *)willAppendAndDisplayMessage:(RCMessage *)message{    return message; }
// 顯示cell前調用
-(void)willDisplayMessageCell:(RCMessageBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath{}
// --------- 未讀消息數UI相關 --------- 

    // 導航欄左側按鈕 未讀消息數(置為nil映之,不再顯示)
    // 需要統(tǒng)計未讀數的會話類型數組
    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表情區(qū)域
    Emoji表情,可修改plist文件
    // 自定義EmojiView
    [self.chatSessionInputBarControl setEmojiBoardView:[RCEmojiBoardView new]];
  1. 擴展區(qū)域
    // 擴展View是否隱藏
    [self.extensionView setHidden:true];

    // 添加item  (tag不要1001~杠输,那是系統(tǒng)預留的)
    [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)
第三步:
    發(fā)送自定義消息

第一步:
自定義消息鹦牛,并注冊(registerMessageType)

    // 創(chuàng)建自定義消息類
    繼承:RCMessageContent (所有消息的基類)搞糕,并實現其遵守的協(xié)議(必須實現,見下)
    // 注冊自定義消息類(必須注冊)
    [[RCIM sharedRCIM]registerMessageType:[RCMessageContent class]];
:RCMessageContent
    該類遵守了RCMessageCoding,RCMessagePersistentCompatible,RCMessageContentView3個協(xié)議曼追,如下


// ---------- 消息內容的編解碼協(xié)議 ---------- 
@protocol RCMessageCoding <NSObject>
@required
// 返回消息唯一標識(不要以RC:開頭)
+(NSString *)getObjectName;
// 消息內容->json
- (NSData *)encode;
// json->消息內容
- (void)decodeWithData:(NSData *)data;
/*
主要有三個功能:
    提供消息唯一標識符
    消息發(fā)送時將消息中的所有信息編碼為 JSON 數據傳輸
    消息接收時將 JSON 數據解碼還原為消息對象
*/
@end

// ---------- 消息內容的存儲協(xié)議 ----------
@protocol RCMessagePersistentCompatible <NSObject>
@required
// 返回消息的存儲策略(在本地是否存儲窍仰、是否計入未讀消息數)
+(RCMessagePersistent)persistentFlag;
/*
用于確定消息內容的存儲策略,指明此消息類型在本地是否存儲礼殊、是否計入未讀消息數驹吮,RCMessagePersistent有4種:
    MessagePersistent_NONE          在本地不存儲,不計入未讀數晶伦。
    MessagePersistent_ISPERSISTED   表示客戶端收到消息后碟狞,要進行未讀消息計數(未讀消息數增加 1),所有內容型消息都應該設置此值婚陪。非內容類消息暫不支持消息計數篷就。
    MessagePersistent_ISCOUNTED     表示客戶端收到消息后,要進行存儲近忙,并在之后可以通過接口查詢竭业。
    MessagePersistent_STATUS        在本地不存儲,不計入未讀數及舍,并且如果對方不在線未辆,服務器會直接丟棄該消息,對方如果之后再上線也不會再收到此消息(聊天室類型除外锯玛,此類消息聊天室會視為普通消息)咐柜。
*/
@end

// ---------- 消息內容摘要的協(xié)議 ---------- 
@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協(xié)議
///消息是否存儲攘残,是否計入未讀數
+ (RCMessagePersistent)persistentFlag {
    return (MessagePersistent_ISPERSISTED | MessagePersistent_ISCOUNTED);
}

#pragma mark NSCoding協(xié)議
/// 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協(xié)議
/// 會話列表中顯示的最新消息內容
- (NSString *)conversationDigest {
    return @"[已發(fā)貨]";
}

#pragma mark RCMessageCoding協(xié)議
///將消息內容編碼成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內容區(qū)域之外的高度
    return CGSizeMake(100, 100);
}
4. 相關
  1. 用戶信息
  為了信息安全和一致:存儲在App服務器而不是融云服務器牍蜂。
  融云提供了 用戶信息提供者、群組信息提供者泰涂、群名片信息提供者鲫竞,只需實現響應協(xié)議即可正確獲取并顯示信息。

 
 // --------- 群組信息提供者 --------- 
 <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不同

    // 創(chuàng)建討論組(用戶ID)
    [[RCIM sharedRCIM]createDiscussion:@"討論組名稱" userIdList:@[] success:^(RCDiscussion *discussion) {
        // discussion討論組對象
    } error:^(RCErrorCode status) {
        NSLog(@"創(chuàng)建討論組出錯青扔,錯誤碼:%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 通知,沒有人數限制互亮,退出后不再接收消息并清空本地消息記錄犁享。
    融云默認一個用戶同時只能加入一個聊天室。
    一個聊天室一個小時內沒有人發(fā)送消息或者加入時會被自動銷毀豹休。

    // 設置 獲取歷史記錄的條數(默認獲取10條歷史記錄)(-1:不獲取炊昆,0:默認10條,0<count<=50)
    [self setDefaultHistoryMessageCountOfChatRoom:-1];
    
    // 自定義
    // 加入聊天室(不存在則創(chuàng)建)
    [[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);
    }];

系統(tǒng)會話 ConversationType_SYSTEM

  不能從App發(fā)起系統(tǒng)會話,只能通過App服務器來發(fā)起

客服 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. 系統(tǒng)自帶消息類型
文本消息
    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};
    */



    發(fā)送上述消息(會話類型,目標用戶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"];



    發(fā)送上述消息斜姥,存儲有效期為 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. 消息接收監(jiān)聽
    // 消息接收監(jiān)聽 <RCIMReceiveMessageDelegate>  放在AppDelegate中
    [RCIM sharedRCIM].receiveMessageDelegate=self;
    

// 收到消息時調用(此時message已被存儲數據庫惑惶,詳見下圖)
-(void)onRCIMReceiveMessage:(RCMessage *)message left:(int)left{
    // 發(fā)送通知设塔、播放音效凄吏、震動。闰蛔。痕钢。
    // 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);
    }];
時間段屏蔽

    // 設置 時間段屏蔽课梳,(開始屏蔽消息提醒的時間,持續(xù)時間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];
        });
    }
進入聊天頁設置小角標

功能進階部分(紅包椭懊、動態(tài)表情、群組@) 待續(xù)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末步势,一起剝皮案震驚了整個濱河市氧猬,隨后出現的幾起案子,更是在濱河造成了極大的恐慌坏瘩,老刑警劉巖盅抚,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異倔矾,居然都是意外死亡妄均,警方通過查閱死者的電腦和手機柱锹,發(fā)現死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丰包,“玉大人禁熏,你說我怎么就攤上這事∫乇耄” “怎么了瞧毙?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寄症。 經常有香客問我宙彪,道長,這世上最難降的妖魔是什么瘸爽? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任您访,我火速辦了婚禮,結果婚禮上剪决,老公的妹妹穿的比我還像新娘灵汪。我一直安慰自己,他們只是感情好柑潦,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布享言。 她就那樣靜靜地躺著,像睡著了一般渗鬼。 火紅的嫁衣襯著肌膚如雪览露。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天譬胎,我揣著相機與錄音差牛,去河邊找鬼。 笑死堰乔,一個胖子當著我的面吹牛偏化,可吹牛的內容都是我干的。 我是一名探鬼主播镐侯,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼侦讨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了苟翻?” 一聲冷哼從身側響起韵卤,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎崇猫,沒想到半個月后沈条,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡邓尤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年拍鲤,在試婚紗的時候發(fā)現自己被綠了贴谎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汞扎。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡季稳,死狀恐怖,靈堂內的尸體忽然破棺而出澈魄,到底是詐尸還是另有隱情景鼠,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布痹扇,位于F島的核電站铛漓,受9級特大地震影響,放射性物質發(fā)生泄漏鲫构。R本人自食惡果不足惜浓恶,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望结笨。 院中可真熱鬧包晰,春花似錦、人聲如沸炕吸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赫模。三九已至树肃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瀑罗,已是汗流浹背胸嘴。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留斩祭,地道東北人劣像。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像停忿,于是被迫代替她去往敵國和親驾讲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容