集成環信3.0,入坑指南,看這一篇就夠

版權聲明:本文為博主原創文章,未經博主允許不得轉載。

前言

公司最近要求做即時通訊, 直接用了三方環信了,今天和大家談談關于 我做環信集成的過程和坑點,有什么不足的地方,還望大家多多指正

與環信V2.0的區別

既然要使用三方環信,第一步當然是下載官方demo了,在這里我用的版本是環信V3.3.2 , 通過查看官方文檔我們不難發現, 相比于之前的環信2.0, 環信3.0 中的核心類為 EMClient 類,通過 EMClient 類可以獲取到 chatManagergroupManager、contactManager、roomManager對象。原來 2.0 版本的 SDK 很多方法提供了同步、異步回調、異步(block)三種方法,3.0 版只提供同步方法(async開頭的方法為異步方法)
** 我們只需要知道 2.0版本 [EaseMob shareInstance] → 3.0 版本 [EMClient sharedClient] **

大家可以根據不同的需求選擇不同的模塊
  • EMClient: 是 SDK 的入口,主要完成登錄、退出、連接管理等功能。也是獲取其他模塊的入口。
  • EMChatManager: 管理消息的收發,完成會話管理等功能。
  • EMContactManager: 負責好友的添加刪除,黑名單的管理。
  • EMGroupManager: 負責群組的管理,創建、刪除群組,管理群組成員等功能。
  • EMChatroomManager: 負責聊天室的管理。

準備工作

  • 注冊環信開發者賬號并創建后臺應用
  • 制作并上傳推送證書來實現離線推送功能
  • 導入SDK,這里推薦使用CocoaPods進行導入,其中有兩個版本供大家選擇:HyphenateLiteHyphenate 其中后者包含了實時語音
    這里我們就不過多闡述了,在這里附上官方的SDK集成網址供大家參考
    集成iOS SDK前準備工作
    iOS的SDK導入

初始化SDK,以及登錄,注冊,自動登錄,退出登錄

*在.pch文件中我們引用 #import <Hyphenate/Hyphenate.h> *
在AppDelegate.m中:

//1.初始化SDK
    //NSLog(@"環信做自動登錄時沙盒路徑%@",NSHomeDirectory());
    //AppKey:注冊的AppKey,詳細見下面注釋。
    //apnsCertName:推送證書名(不需要加后綴),詳細見下面注釋。
    EMOptions *options = [EMOptions optionsWithAppkey:HUANXIN_APPKEY];
    //    options.apnsCertName = @"istore_dev";
    EMError *error = [[EMClient sharedClient] initializeSDKWithOptions:options];
    if (!error) {
        NSLog(@"環信初始化成功");
    }

在登錄頁面LoginViewController.m中:

//因為設置了自動登錄模式,所以登錄之前要注銷之前的用戶,否則重復登錄會拋出異常
  EMError *error1 = [[EMClient sharedClient] logout:YES];
    if (!error1) {
        NSLog(@"退出之前的用戶成功");
    }
[[EMClient sharedClient] loginWithUsername:_userTextField.text password:_passTextField.text completion:^(NSString *aUsername, EMError *aError){
        if (!aError) {
            kSetLogin(YES);
            NSLog(@"登陸成功,用戶名為:%@",aUsername);
            // 添加菊花 [custom showWaitView:@"登錄中..." byView:self.view completion:^{
           // 設置自動登錄
            [EMClient sharedClient].options.isAutoLogin = YES;

            //  }];
        } else {
            NSLog(@"登陸失敗%d",aError.code);  //這里可以通過EMError這個類,去查看登錄失敗的原因
        }
    }];

在注冊頁面RegisterViewController.m中:

//如果注冊不成功,需要去環信官網切換注冊模式為開放注冊,而不是授權注冊
    EMError *error = [[EMClient sharedClient] registerWithUsername:_userTextField.text password:_passTextField.text];
    if (error == nil) {
        NSLog(@"注冊成功");
        kSetLogin(YES);
//這里是注冊的時候在調用登錄方法, 讓其登錄一次,只有這樣下次才能自動登錄,只設置自動登錄的Boll值是不行的 
//也就是說這里的邏輯是一旦讓用戶注冊,如果注冊成功直接跳轉到我的頁面,并設置下次自動登錄,并不是注冊完成后回到登錄頁面
        [[EMClient sharedClient] loginWithUsername:_userTextField.text password:_passTextField.text completion:^(NSString *aUsername, EMError *aError) {
            [EMClient sharedClient].options.isAutoLogin = YES;
        }];
        
        MineViewController *mineVC = [MineViewController new];
        mineVC.hidesBottomBarWhenPushed = YES;
        for (UIViewController *vc in self.navigationController.viewControllers) {
            if ([vc isKindOfClass:[MineViewController class]]) {
                [self.navigationController popToViewController:vc animated:YES];
            }
        }
    }else{
        NSLog(@"注冊失敗%d",error.code);
    }

設置自動登錄的代理,以及實現邏輯,在AppDelegate.m中:

//2.監聽自動登錄的狀態
    //設置代理
    [[EMClient sharedClient] addDelegate:self delegateQueue:nil];
    
    //3.如果登錄過,直接來到主界面
    BOOL isAutoLogin = [EMClient sharedClient].options.isAutoLogin;
    jLog(@"登錄狀態為:%d",isAutoLogin);
    
    if (isAutoLogin == YES) {
        self.window.rootViewController = [BaseTabBarController new];
    }else{
        //部分APP這里就是返回登錄頁面, 這里就不做操作了
        NSLog(@"環信自動登錄失敗,或者是沒有登陸過");
    }

需要注意的是:添加代理一定不要忘了移除代理,這個暫且算一個小小的注意點

//移除代理, 因為這里是多播機制
- (void)dealloc {
    [[EMClient sharedClient] removeDelegate:self];
}


//自動登錄的回調
- (void)autoLoginDidCompleteWithError:(EMError *)aError{
    if (!aError) {
        NSLog(@"自動登錄成功");
        [CustomView alertMessage:@"環信自動登錄成功" view:self.window];
    }else{
        NSLog(@"自動登錄失敗%d",aError.code);
    }
}


/**
 環信 監聽網絡狀態(重連)
 1.登錄成功后,手機無法上網時
 2.登錄成功后,網絡狀態變化時
 aConnectionState:當前狀態
 */
- (void)didConnectionStateChanged:(EMConnectionState)aConnectionState{
    if (aConnectionState == EMConnectionConnected) {
        NSLog(@"網絡連接成功");
    }else{
        NSLog(@"網絡斷開");
        //監聽網絡狀態(這里通知的目地是檢測到如果沒網絡的情況下,修改Navigation.title的值)
        [[NSNotificationCenter defaultCenter] postNotificationName:
        AFNetworkingReachabilityDidChangeNotification object:nil];
    }
}

/*!
 *  重連
 *  有以下幾種情況,會引起該方法的調用:
 *  1. 登錄成功后,手機無法上網時,會調用該回調
 *  2. 登錄成功后,網絡狀態變化時,會調用該回調
 */
- (void)connectionStateDidChange:(EMConnectionState)aConnectionState{
    NSLog(@"斷線重連不需要其他操作%u",aConnectionState);
}


//APP進入后臺
- (void)applicationDidEnterBackground:(UIApplication *)application {
    [[EMClient sharedClient] applicationDidEnterBackground:application];
}

//APP將要從后臺返回
- (void)applicationWillEnterForeground:(UIApplication *)application {
    [[EMClient sharedClient] applicationWillEnterForeground:application];
}

最后是退出登錄:

- (void)quitLogin:(UIButton *)button {
    custom = [CustomView new];
    if (LOGIN) {
        [self alertWithTitle:nil message:@"是否確定退出登錄?" actionATitle:@"確定" actionAHandler:^(UIAlertAction *action) {
            [UserInfoClass clearAllInfo];
            [UserInfoClass printAllInfo];
            NSLog(@"%@",[NSThread currentThread]);
            //退出登錄
            [[CustomView new] showWaitView:@"退出登錄成功" byView:self.view completion:^{
                [[EMClient sharedClient] logout:YES completion:^(EMError *aError) {
                    if (!aError) {
                        NSLog(@"退出環信登錄成功");
                    }else{
                        NSLog(@"退出環信登錄失敗,%u",aError.code);
                    }
                }];
                
                [self.navigationController popViewControllerAnimated:YES];
            }];
        } actionBTitle:@"取消" actionBHandler:nil totalCompletion:nil];
    } else {
        [custom showAlertView:@"您尚未登錄" byView:self.view completion:nil];
    }
}

進行到這里以后,相信大家就能實現簡單的登錄,注冊以及自動登錄了,是不是也比較簡單呢,接下來簡單說一下在登錄,注冊過程中遇到的問題。

  1. 引用頭文件的時候報錯出現:Hyphenate/EMSDK.h’ file no found
    解決方法: 換下引用#import <HyphenateLite/HyphenateLite.h>
    或者#import <Hyphenate/Hyphenate.h>

    如果此方法不行, 可以試試選中你的項目中的Pods -> EaseUI->Build Phases->Link Binary With Libraries ,點?->Add Other ,找到工程里面,Pods里面的Hyphenate文件夾下面的Hyphenate.framework 點擊open,重新編譯就好了
  2. 真機上登錄,注冊沒有效果
    解決方法: 點擊工程名進入工程設置 -> BuildSettings -> 搜索bitcode -> 將Enable Bitcode設置為NO
  3. 集成動態庫上傳AppStore出現問題, 打包上線時報錯
    ERROR ITMS-90087: "Unsupported Architectures. The executable for xiantaiApp.app/Frameworks/Hyphenate.framework contains unsupported architectures '[x86_64, i386]'."
    解決方法: 環信:由于 iOS 編譯的特殊性,為了方便開發者使用,我們將 i386 x86_64 armv7 arm64 幾個平臺都合并到了一起,所以使用動態庫上傳appstore時需要將i386 x86_64兩個平臺刪除后,才能正常提交審核
    在SDK當前路徑下執行以下命令刪除i386 x86_64兩個平臺

    iOS的SDK導入中有詳細地說明,拿實時音視頻版本版本為例 : 執行完以上命令如圖所示
    運行完畢后得到的Hyphenate.framework就是最后的結果,拖進工程,編譯打包上架。
    注意 : 最后得到的包必須真機編譯運行,并且工程要設置編譯二進制文件General->Embedded Bunaries.
    刪除i386、x86_64平臺后,SDK會無法支持模擬器編譯,只需要在上傳AppStore時在進行刪除,上傳后,替換為刪除前的SDK,建議先分別把i386、x86_64、arm64、armv7各平臺的包拆分到本地,上傳App Store時合并arm64、armv7平臺,并移入Hyphenate.framework內。上傳后,重新把各平臺包合并移入動態庫
  4. 依舊是打包錯誤: ERROR ITMS-90535: "Unexpected CFBundleExecutable Key. 。。。。。。 consider contacting the developer of the framework for an update to address this issue."
    解決方法: 從EaseUIResource.bundle中找到info.plist刪掉CFBundleExecutable,或者整個info.plist刪掉

接下來我們說一下,會話聊天部分和會話列表的兩個部分
這里用到的是EaseUI ,它封裝了 IM 功能常用的控件(如聊天會話、會話列表、聯系人列表)

集成EaseUI

請戳這里查看 → EaseUI使用指南
在這里集成EaseUI的時候,有兩種方法:

  1. 使用cocoapods導入 pod 'EaseUI', :git => 'https://github.com/easemob/easeui-ios-hyphenate-cocoapods.git', :tag => '3.3.2'(這里我推薦使用第一種,比較省事,簡單)
  2. 手動導入文件直接將EaseUI拖入已經集成SDK的項目中(注意: 由于EaseUI中有幾個常用的第三方庫 MJRefresh SDWebImage MBProgressHUD。這會跟自己項目中的沖突。

我們先來看看使用第一種方法集成時候的過程和遇到的坑點:

坑點1: 使用cocoaPods時候,出現了報錯的信息,發現無法將環信的EaseUI導入。
這時候我們跟隨提示的指令進行更新pods就可以了,主要是pod 問題 本地倉庫太舊了, 終端執行pod repo update, 之后在pod search 'Hyphenate' 如果可以找到3.3.0版本, 就可以下載了 podfile 里面 platform 要指定8.0

在導入完成以后,在.pch文件中引用了#import <EaseUI/EaseUI.h>,編譯,恩,居然沒有報錯,看來可以進行下一步了
直接在AppDelegate.m中初始化EaseUI:

[[EaseSDKHelper shareHelper] hyphenateApplication:application
                        didFinishLaunchingWithOptions:launchOptions
                                               appkey:HUANXIN_APPKEY
                                         apnsCertName:nil
                                          otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];

這時,當我滿懷信心跑起來了工程,納尼??不能自動登錄了,每次必須退出登錄以后,再登錄一次以后才能實現自動登錄,然后當我第二次運行工程的時候發現自動登錄又失效了,什么鬼?!

2C83F9DCCF6D31A0960BCCC31D46A933.jpg

坑點2: 直接登錄不能發送消息, 必須自動登錄以后才能發送接收,自動登錄大部分時候會走失敗的回調
最后依靠萬能的環信客服人員提供了技術支持,不得不說環信的客服還是很給力的
WX20170614-165444@2x.png

原來是使用pods導入了兩個版本的SDK,使用pods導入的同學們一定要注意這個問題啊,不要重復導入,不然會出現許多未知的bug,
I27FF4PMBQQCP{PLIQ{5E4B.jpg

接下來我們看一下第二種方法:手動導入EaseUI

  1. 首先我們根據下載好的環信demo中的文件拖入到工程中,

    如果要是集成紅包功能,就加上RedacketSDK

  2. 把demo中的pch文件 拷貝到自己的pch文件中,并且在自己所有的pch文件的頭和尾添加
#ifdef __OBJC__
//
#endif
  1. 編譯后,工程會出現如下錯誤:


    WX20170614-171907@2x.png

這個是因為用到了UIKit里的類,但是只導入了Foundation框架,這個錯誤在其他類里也會出現,我們可以手動修改Founfation為UIKit,但是我不建議這么做,第一這個做法的工程量比較大, 在其他類里面也要導入,二,不利于移植,當以后環信更新的時候我們還是需要做同樣的操作,這里我的做法的創建一個pch文件,在pch文件里面導入UIKit。
解決辦法:建一個PCH文件在里面添加如下代碼:

#ifdef __OBJC__
    #import <UIKit/UIKit.h>
    #import <Foundation/Foundation.h>
#define NSEaseLocalizedString(key, comment) [[NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:@"EaseUIResource" withExtension:@"bundle"]] localizedStringForKey:(key) value:@"" table:nil]

#endif

這里需要注意一定要加入--OBJC --,不然可能會報NSObjcRunTime的錯誤

4.環信內部集成的MBProgressHUD SDWebImage MJRefresh 與我們工程中集成的這幾個第三方庫發生沖突!
解決方法:刪掉工程中自己集成的這些第三方庫,或者刪除環信EaseUI 里面的這些第三方庫!
需要注意的是:如果刪除的是環信集成的第三方庫!由于環信在集成的第三方庫中加了EM前綴! 記得刪掉EaseUI 中使用方法的前綴,不然會報錯!

如果集成的是不包含實時音視頻的SDK , 手動導入EaseUI的話 , 那么此時還會報Hyphenate/EMSDK.h’ file no found
這時需要把 #import <Hyphenate/Hyphenate.h>注釋掉,然后把報錯地方的Hyphenate換成HyphenateLite就可以了,和上面提到的第一點是一樣的
到這里以后,應該沒有什么問題,編譯如果成功的話,那么恭喜你了

至此,我們就導入了EaseUI并在appDelegate.m中初始化了EaseUI,接下來我們就先來完善聊天的頁面

聊天頁面部分

EaseUI集成應用其實簡單很多很多,里面也封裝了關于頭像昵稱的設置,所需要做的只是把代理方法實現,前提是你的聊天頁面等都是繼承EaseUI里面的相關的類去做的。

這里給大家推薦環信官方論壇的一個快速集成聊天的網址:IOS快速集成環信IM - 基于官方的Demo優化,5分鐘集成環信IM功能

E04D3C27-B372-4D17-B801-A50C1E395589.jpeg

由于環信官方只是通過用戶名的id進行會話,所以不是好友也可以進行聊天,我們先做一個簡單的單聊頁面,如圖 (PS:用戶頭像環信并不進行存儲,所以我們后期實現代理方法進行處理就可以了)

首先我們創建一個ChatViewController類并繼承于EaseMessageViewController
在ChatViewController.m中:

@interface ChatViewController () 
<
  UIAlertViewDelegate,
  EaseMessageViewControllerDelegate, 
  EaseMessageViewControllerDataSource,
  EMClientDelegate,
  UIImagePickerControllerDelegate
>
{
    UIMenuItem *_copyMenuItem;
    UIMenuItem *_deleteMenuItem;
    UIMenuItem *_transpondMenuItem;
}

@property (nonatomic) BOOL isPlayingAudio;

@property (nonatomic) NSMutableDictionary *emotionDic; //表情

@end

在ViewDidLoad的方法中:我們修改環信的一些設置,讓他更符合我們的開發需求

- (void)viewDidLoad {
    [super viewDidLoad];

    self.showRefreshHeader = YES;
    self.delegate = self;
    self.dataSource = self;
    
    if ([[DeviceInfo SystemVersion] floatValue] >= 7.0) {
        self.edgesForExtendedLayout =  UIRectEdgeNone;
    }
    self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    
    //修改聊天界面的顏色
    //    self.view.backgroundColor = [UIColor colorWithString:@"#f8f8f8"];
    
    //自定義氣泡
    [[EaseBaseMessageCell appearance] setSendBubbleBackgroundImage:[[UIImage imageNamed:@"右氣泡"] stretchableImageWithLeftCapWidth:5 topCapHeight:35]];
    [[EaseBaseMessageCell appearance] setRecvBubbleBackgroundImage:[[UIImage imageNamed:@"左氣泡"] stretchableImageWithLeftCapWidth:35 topCapHeight:35]];
    
    //設置頭像圓角
    [[EaseBaseMessageCell appearance] setAvatarSize:40.f];
    [[EaseBaseMessageCell appearance] setAvatarCornerRadius:20.f];
    
    //隱藏對話時的昵稱
    [EaseBaseMessageCell appearance].messageNameIsHidden = YES;
    //修改字體高度,這樣在隱藏昵稱的時候,可以讓氣泡對齊
    [EaseBaseMessageCell appearance].messageNameHeight = 10;
    
    //修改發送圖片,定位,等的所在的View的顏色...
    [[EaseChatBarMoreView appearance] setMoreViewBackgroundColor:[UIColor colorWithRed:240 / 255.0 green:242 / 255.0 blue:247 / 255.0 alpha:1.0]];
    //    [[EaseChatBarMoreView appearance] setMoreViewBackgroundColor:[UIColor colorWithString:@"#0a0a0a"]];
    
    //刪除功能模塊中的實時通話
    [self.chatBarMoreView removeItematIndex:3];

    //刪除功能模塊中的錄制視頻(注意:刪除通話以后,視頻的索引變成了3,所以這里還是3哦)
    [self.chatBarMoreView removeItematIndex:3];
    
    //更改功能模塊中的圖片和文字
    [self.chatBarMoreView updateItemWithImage:[UIImage imageNamed:@"information_photo"] highlightedImage:[UIImage imageNamed:@"information_photo_hl"] title:@"照片" atIndex:0];
    [self.chatBarMoreView updateItemWithImage:[UIImage imageNamed:@"information_location"] highlightedImage:[UIImage imageNamed:@"information_location_hl"] title:@"位置" atIndex:1];
    [self.chatBarMoreView updateItemWithImage:[UIImage imageNamed:@"information_photograph"] highlightedImage:[UIImage imageNamed:@"information_photograph_hl"] title:@"拍攝" atIndex:2];
    
    //設置按住說話的圖片數組
//    NSArray *arr = @[@"information_voice_one",@"information_voice_two",@"information_voice_three",@"information_voice_four",@"information_voice_five",kDefaultUserHeadImage];
//    [self.recordView setVoiceMessageAnimationImages:arr];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteAllMessages:) name:KNOTIFICATIONNAME_DELETEALLMESSAGE object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(exitChat) name:@"ExitGroup" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(insertCallMessage:) name:@"insertCallMessage" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleCallNotification:) name:@"callOutWithChatter" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleCallNotification:) name:@"callControllerClose" object:nil];
    
    //通過會話管理者獲取已收發消息 (bug:會話列表已經調用了刷新,如果繼續調用的話會出現消息重復的現象)
//    [self tableViewDidTriggerHeaderRefresh];
    
    //處理表情崩潰
    //    EaseEmotionManager *manager = [[EaseEmotionManager alloc] initWithType:(EMEmotionDefault) emotionRow:3 emotionCol:7 emotions:[EaseEmoji allEmoji]];
    //    [self.faceView setEmotionManagers:@[manager]];
    
    //語音動態圖片數組
    /* NSArray *array = [[NSArray alloc]initWithObjects:
        [UIImage imageNamed:@"chat_sender_audio_playing_full"],
        [UIImage imageNamed:@"chat_sender_audio_playing_000"], 
        [UIImage imageNamed:@"chat_sender_audio_playing_001"], 
        [UIImage imageNamed:@"chat_sender_audio_playing_002"], 
        [UIImage imageNamed:@"chat_sender_audio_playing_003"], 
        nil];
*/
    //    [[EaseBaseMessageCell appearance] setSendMessageVoiceAnimationImages:array];
    /*    NSArray * array1 = [[NSArray alloc] initWithObjects:
          [UIImage imageNamed:@"chat_receiver_audio_playing_full"],
          [UIImage imageNamed:@"chat_receiver_audio_playing000"], 
          [UIImage imageNamed:@"chat_receiver_audio_playing001"], 
          [UIImage imageNamed:@"chat_receiver_audio_playing002"], 
          [UIImage imageNamed:@"chat_receiver_audio_playing003"],nil];
*/
    //    [[EaseBaseMessageCell appearance] setRecvMessageVoiceAnimationImages:array1];
}

這里要注意的是更改功能模塊中的圖片和文字的時候,文字是沒有效果的,源碼中沒有添加Label的代碼,需要我們自己去寫,可以添加分類,也可以直接在源碼上改,我這里由于只是多了Label而已,所以是直接在源碼上改的

在EaseChatBarMoreView.m中,下面的方法中添加Label即可

- (void)updateItemWithImage:(UIImage *)image highlightedImage:(UIImage *)highLightedImage title:(NSString *)title atIndex:(NSInteger)index {

對了,如果要修改ChatBarMoreView的高度的話,在第220行

if (_maxIndex >=5) {
        frame.size.height = 150;
    } else {
        //  修改高度
        frame.size.height = 120;
    }

在ChatViewController.m中,我們繼續添加:
注意:這里可能會出現發現重復消息。[self tableViewDidTriggerHeaderRefresh]; 檢查一下這個方法是不是在chatViewController 和EaseMessageViewCOntroller 的ViewDidLoad 里面都調用了,看如果都有,隨便刪除一個這個方法。就ok了!

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    if (self.conversation.type == EMConversationTypeGroupChat) {
        if ([[self.conversation.ext objectForKey:@"subject"] length])
        {
            self.title = [self.conversation.ext objectForKey:@"subject"];
        }
    }
}

實現收到消息以后播放音頻以及震動

//收到消息的回調
- (void)messagesDidReceive:(NSArray *)aMessages {
    //收到消息時,播放音頻
    [[EMCDDeviceManager sharedInstance] playNewMessageSound];
    
    //收到消息時, 震動
    [[EMCDDeviceManager sharedInstance] playVibration];
}

根據遵循EaseMessageViewControllerDelegate的代理,實現長按手勢的功能,轉發,復制,刪除如下:

//是否允許長按
- (BOOL)messageViewController:(EaseMessageViewController *)viewController
   canLongPressRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;
}

//觸發長按手勢
- (BOOL)messageViewController:(EaseMessageViewController *)viewController
   didLongPressRowAtIndexPath:(NSIndexPath *)indexPath
{
    id object = [self.dataArray objectAtIndex:indexPath.row];
    if (![object isKindOfClass:[NSString class]]) {
        EaseMessageCell *cell = (EaseMessageCell *)[self.tableView cellForRowAtIndexPath:indexPath];
        [cell becomeFirstResponder];
        self.menuIndexPath = indexPath;
        [self _showMenuViewController:cell.bubbleView andIndexPath:indexPath messageType:cell.model.bodyType];
    }
    return YES;
}
- (void)_showMenuViewController:(UIView *)showInView
                   andIndexPath:(NSIndexPath *)indexPath
                    messageType:(EMMessageBodyType)messageType
{
    if (self.menuController == nil) {
        self.menuController = [UIMenuController sharedMenuController];
    }
    
    if (_deleteMenuItem == nil) {
        _deleteMenuItem = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"刪除", @"Delete") action:@selector(deleteMenuAction:)];
    }
    
    if (_copyMenuItem == nil) {
        _copyMenuItem = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"復制", @"Copy") action:@selector(copyMenuAction:)];
    }
    
    if (_transpondMenuItem == nil) {
        _transpondMenuItem = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"轉發", @"Transpond") action:@selector(transpondMenuAction:)];
    }
    
    if (messageType == EMMessageBodyTypeText) {
        [self.menuController setMenuItems:@[_copyMenuItem, _deleteMenuItem,_transpondMenuItem]];
    } else if (messageType == EMMessageBodyTypeImage){
        [self.menuController setMenuItems:@[_deleteMenuItem,_transpondMenuItem]];
    } else {
        [self.menuController setMenuItems:@[_deleteMenuItem]];
    }
    [self.menuController setTargetRect:showInView.frame inView:showInView.superview];
    [self.menuController setMenuVisible:YES animated:YES];
}
- (void)transpondMenuAction:(id)sender
{
    if (self.menuIndexPath && self.menuIndexPath.row > 0) {
        id<IMessageModel> model = [self.dataArray objectAtIndex:self.menuIndexPath.row];
//        ContactListSelectViewController *listViewController = [[ContactListSelectViewController alloc] initWithNibName:nil bundle:nil];
//        listViewController.messageModel = model;
//        [listViewController tableViewDidTriggerHeaderRefresh];
//        [self.navigationController pushViewController:listViewController animated:YES];
    }
    self.menuIndexPath = nil;
}

- (void)copyMenuAction:(id)sender
{
    UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
    if (self.menuIndexPath && self.menuIndexPath.row > 0) {
        id<IMessageModel> model = [self.dataArray objectAtIndex:self.menuIndexPath.row];
        pasteboard.string = model.text;
    }
    
    self.menuIndexPath = nil;
}

- (void)deleteMenuAction:(id)sender
{
    if (self.menuIndexPath && self.menuIndexPath.row > 0) {
        id<IMessageModel> model = [self.dataArray objectAtIndex:self.menuIndexPath.row];
        NSMutableIndexSet *indexs = [NSMutableIndexSet indexSetWithIndex:self.menuIndexPath.row];
        NSMutableArray *indexPaths = [NSMutableArray arrayWithObjects:self.menuIndexPath, nil];
        
        [self.conversation deleteMessageWithId:model.message.messageId error:nil];
        [self.messsagesSource removeObject:model.message];
        
        if (self.menuIndexPath.row - 1 >= 0) {
            id nextMessage = nil;
            id prevMessage = [self.dataArray objectAtIndex:(self.menuIndexPath.row - 1)];
            if (self.menuIndexPath.row + 1 < [self.dataArray count]) {
                nextMessage = [self.dataArray objectAtIndex:(self.menuIndexPath.row + 1)];
            }
            if ((!nextMessage || [nextMessage isKindOfClass:[NSString class]]) && [prevMessage isKindOfClass:[NSString class]]) {
                [indexs addIndex:self.menuIndexPath.row - 1];
                [indexPaths addObject:[NSIndexPath indexPathForRow:(self.menuIndexPath.row - 1) inSection:0]];
            }
        }
        
        [self.dataArray removeObjectsAtIndexes:indexs];
        [self.tableView beginUpdates];
        [self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
        [self.tableView endUpdates];
        
        if ([self.dataArray count] == 0) {
            self.messageTimeIntervalTag = -1;
        }
    }
    
    self.menuIndexPath = nil;
}

添加表情,并發送,這里我并沒有遇到其他同學說的表情發送崩潰的問題,不過還是將解決方法貼出來,在ViewDidLoad中,大家可以看一下

//獲取表情列表
- (NSArray*)emotionFormessageViewController:(EaseMessageViewController *)viewController
{
    NSMutableArray *emotions = [NSMutableArray array];
    for (NSString *name in [EaseEmoji allEmoji]) {
        EaseEmotion *emotion = [[EaseEmotion alloc] initWithName:@"" emotionId:name emotionThumbnail:name emotionOriginal:name emotionOriginalURL:@"" emotionType:EMEmotionDefault];
        [emotions addObject:emotion];
    }
    EaseEmotion *temp = [emotions objectAtIndex:0];
    EaseEmotionManager *managerDefault = [[EaseEmotionManager alloc] initWithType:EMEmotionDefault emotionRow:3 emotionCol:7 emotions:emotions tagImage:[UIImage imageNamed:temp.emotionId]];
    
    NSMutableArray *emotionGifs = [NSMutableArray array];
    _emotionDic = [NSMutableDictionary dictionary];
    NSArray *names = @[@"icon_002",@"icon_007",@"icon_010",@"icon_012",@"icon_013",@"icon_018",@"icon_019",@"icon_020",@"icon_021",@"icon_022",@"icon_024",@"icon_027",@"icon_029",@"icon_030",@"icon_035",@"icon_040"];
    int index = 0;
    for (NSString *name in names) {
        index++;
        EaseEmotion *emotion = [[EaseEmotion alloc] initWithName:[NSString stringWithFormat:@"[表情%d]",index] emotionId:[NSString stringWithFormat:@"em%d",(1000 + index)] emotionThumbnail:[NSString stringWithFormat:@"%@_cover",name] emotionOriginal:[NSString stringWithFormat:@"%@",name] emotionOriginalURL:@"" emotionType:EMEmotionGif];
        [emotionGifs addObject:emotion];
        [_emotionDic setObject:emotion forKey:[NSString stringWithFormat:@"em%d",(1000 + index)]];
    }
    EaseEmotionManager *managerGif= [[EaseEmotionManager alloc] initWithType:EMEmotionGif emotionRow:2 emotionCol:4 emotions:emotionGifs tagImage:[UIImage imageNamed:@"icon_002_cover"]];
    
    return @[managerDefault,managerGif];
    
}

//判斷消息是否為表情消息
- (BOOL)isEmotionMessageFormessageViewController:(EaseMessageViewController *)viewController
                                    messageModel:(id<IMessageModel>)messageModel
{
    BOOL flag = NO;
    if ([messageModel.message.ext objectForKey:MESSAGE_ATTR_IS_BIG_EXPRESSION]) {
        return YES;
    }
    return flag;
}

//根據消息獲取表情信息
- (EaseEmotion*)emotionURLFormessageViewController:(EaseMessageViewController *)viewController
                                      messageModel:(id<IMessageModel>)messageModel
{
    NSString *emotionId = [messageModel.message.ext objectForKey:MESSAGE_ATTR_EXPRESSION_ID];
    EaseEmotion *emotion = [_emotionDic objectForKey:emotionId];
    if (emotion == nil) {
        emotion = [[EaseEmotion alloc] initWithName:@"" emotionId:emotionId emotionThumbnail:@"" emotionOriginal:@"" emotionOriginalURL:@"" emotionType:EMEmotionGif];
    }
    return emotion;
}

//獲取發送表情消息的擴展字段
- (NSDictionary*)emotionExtFormessageViewController:(EaseMessageViewController *)viewController
                                        easeEmotion:(EaseEmotion*)easeEmotion
{
    return @{MESSAGE_ATTR_EXPRESSION_ID:easeEmotion.emotionId,MESSAGE_ATTR_IS_BIG_EXPRESSION:@(YES)};
}

//view標記已讀
- (void)messageViewControllerMarkAllMessagesAsRead:(EaseMessageViewController *)viewController
{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"setupUnreadMessageCount" object:nil];
}

最后就是實現ViewDidLoad中的通知了,這里的通知是刪除所有會話,以及對于實時語音的一些實現,沒有這些需求的同學們可以略過

#pragma mark - EMClientDelegate
//當前登錄賬號在其它設備登錄時會接收到此回調
- (void)userAccountDidLoginFromOtherDevice
{
    if ([self.imagePicker.mediaTypes count] > 0 && [[self.imagePicker.mediaTypes objectAtIndex:0] isEqualToString:(NSString *)kUTTypeMovie]) {
        [self.imagePicker stopVideoCapture];
    }
}

//當前登錄賬號已經被從服務器端刪除時會收到該回調
- (void)userAccountDidRemoveFromServer
{
    if ([self.imagePicker.mediaTypes count] > 0 && [[self.imagePicker.mediaTypes objectAtIndex:0] isEqualToString:(NSString *)kUTTypeMovie]) {
        [self.imagePicker stopVideoCapture];
    }
}

//服務被禁用
- (void)userDidForbidByServer
{
    if ([self.imagePicker.mediaTypes count] > 0 && [[self.imagePicker.mediaTypes objectAtIndex:0] isEqualToString:(NSString *)kUTTypeMovie]) {
        [self.imagePicker stopVideoCapture];
    }
}

- (void)showGroupDetailAction
{
    [self.view endEditing:YES];
//    if (self.conversation.type == EMConversationTypeGroupChat) {
//        EMGroupInfoViewController *infoController = [[EMGroupInfoViewController alloc] initWithGroupId:self.conversation.conversationId];
//        [self.navigationController pushViewController:infoController animated:YES];
//    }
//    else if (self.conversation.type == EMConversationTypeChatRoom)
//    {
//        ChatroomDetailViewController *detailController = [[ChatroomDetailViewController alloc] initWithChatroomId:self.conversation.conversationId];
//        [self.navigationController pushViewController:detailController animated:YES];
//    }
}

- (void)deleteAllMessages:(id)sender
{
    if (self.dataArray.count == 0) {
        [self showHint:NSLocalizedString(@"message.noMessage", @"no messages")];
        return;
    }
    
    if ([sender isKindOfClass:[NSNotification class]]) {
        NSString *groupId = (NSString *)[(NSNotification *)sender object];
        BOOL isDelete = [groupId isEqualToString:self.conversation.conversationId];
        if (self.conversation.type != EMConversationTypeChat && isDelete) {
            self.messageTimeIntervalTag = -1;
            [self.conversation deleteAllMessages:nil];
            [self.messsagesSource removeAllObjects];
            [self.dataArray removeAllObjects];
            
            [self.tableView reloadData];
            [self showHint:NSLocalizedString(@"message.noMessage", @"no messages")];
        }
    }
    else if ([sender isKindOfClass:[UIButton class]]){
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"prompt", @"Prompt") message:NSLocalizedString(@"sureToDelete", @"please make sure to delete") delegate:self cancelButtonTitle:NSLocalizedString(@"cancel", @"Cancel") otherButtonTitles:NSLocalizedString(@"ok", @"OK"), nil];
        [alertView show];
    }
}

- (void)exitChat
{
    [self.navigationController popToViewController:self animated:NO];
    [self.navigationController popViewControllerAnimated:YES];
}

- (void)insertCallMessage:(NSNotification *)notification
{
    id object = notification.object;
    if (object) {
        EMMessage *message = (EMMessage *)object;
        [self addMessageToDataSource:message progress:nil];
        [[EMClient sharedClient].chatManager importMessages:@[message] completion:nil];
    }
}

- (void)handleCallNotification:(NSNotification *)notification
{
    id object = notification.object;
    if ([object isKindOfClass:[NSDictionary class]]) {
        //開始call
        self.isViewDidAppear = NO;
    } else {
        //結束call
        self.isViewDidAppear = YES;
    }
}

截止到目前為止,聊天頁面基本上就差不多了,這里需要重點說明的是聊天頁面頭像的數據處理

在這里環信給出了2種處理頭像的方法,讓我們一起來看一下,昵稱和頭像的顯示與更新

方法一:從APP服務器獲取昵稱和頭像
  • 昵稱和頭像的獲取:當收到一條消息(群消息)時,得到發送者的用戶ID,然后查找手機本地數據庫是否有此用戶ID的昵稱和頭像,如沒有則調用APP服務器接口通過用戶ID查詢出昵稱和頭像,然后保存到本地數據庫和緩存,下次此用戶發來信息即可直接查詢緩存或者本地數據庫,不需要再次向APP服務器發起請求。

  • 昵稱和頭像的更新:當點擊發送者頭像時加載用戶詳情時從APP服務器查詢此用戶的具體信息然后更新本地數據庫和緩存。當用戶自己更新昵稱或頭像時,也可以發送一條透傳消息到其他用戶和用戶所在的群,來更新該用戶的昵稱和頭像。

方法二:從消息擴展中獲取昵稱和頭像
  • 昵稱和頭像的獲取:把用戶基本的昵稱和頭像的URL放到消息的擴展中,通過消息傳遞給接收方,當收到一條消息時,則能通過消息的擴展得到發送者的昵稱和頭像URL,然后保存到本地數據庫和緩存。當顯示昵稱和頭像時,請從本地或者緩存中讀取,不要直接從消息中把賦值拿給界面(否則當用戶昵稱改變后,同一個人會顯示不同的昵稱)。

  • 昵稱和頭像的更新:當擴展消息中的昵稱和頭像 URI 與當前本地數據庫和緩存中的相應數據不同的時候,需要把新的昵稱保存到本地數據庫和緩存,并下載新的頭像并保存到本地數據庫和緩存。

這里我們選擇使用方案二,首先我們要實現存儲的功能,通過FMDB實現對用戶model的存儲,這里大家可以根據自己的需求進行存儲相關信息,在登錄成功之后你得先把自己的信息存儲起來,在更改了個人資料之后,你要更新這里的存儲信息。這樣就可以做到更新頭像后歷史的頭像也會更新**

簡單來說:流程是這樣的,存儲用戶的model信息 → 把用戶信息擴展附加到要發送的消息中去 → 接收到消息以后通過數據源方法賦值到頭像上去

#pragma mark - EaseMessageViewControllerDataSource  
// 數據源方法  
- (id<IMessageModel>)messageViewController:(EaseMessageViewController *)viewController  
                           modelForMessage:(EMMessage *)message{  
      
    id<IMessageModel> model = nil;  
    // 根據聊天消息生成一個數據源Model  
    //NSLog(@"-======%@",message.from);  
    //debugObj(message.ext);  
      
    model = [[EaseMessageModel alloc] initWithMessage:message];  
    NSDictionary * messageDic = message.ext;  
      
    UserInfoModel * userinfoModel = [ChatUserDataManagerHelper queryByuserEaseMobId:messageDic[CHATUSERID]];  
      
    if (userinfoModel != nil) {  
          
        model.nickname      = userinfoModel.usernickName;  
        model.avatarURLPath = userinfoModel.userHeaderImageUrl;  
    }  
    // 默認頭像  
    //model.avatarImage = [UIImage imageNamed:@"EaseUIResource.bundle/user"];  
    //Placeholder image for network error  
    //項目圖片取出錯誤的時候就用這張代替  
    model.failImageName = @"icon_Default-Avatar";  
    return model;  
}  

這里在貼兩個代理方法,供大家查看

/*!
 @method
 @brief 獲取消息自定義cell
 @discussion 用戶根據messageModel判斷是否顯示自定義cell。返回nil顯示默認cell,否則顯示用戶自定義cell
 @param tableView 當前消息視圖的tableView
 @param messageModel 消息模型
 @result 返回用戶自定義cell
 */
- (UITableViewCell *)messageViewController:(UITableView *)tableView
                       cellForMessageModel:(id<IMessageModel>)messageModel {
    return nil;
}

/*!
 @method
 @brief 點擊消息頭像
 @discussion 獲取用戶點擊頭像回調
 @param viewController 當前消息視圖
 @param messageModel 消息模型
 */
- (void)messageViewController:(EaseMessageViewController *)viewController
  didSelectAvatarMessageModel:(id<IMessageModel>)messageModel
{
    NSLog(@"點擊頭像回調");
    //    UserProfileViewController *userprofile = [[UserProfileViewController alloc] initWithUsername:messageModel.message.from];
    //    [self.navigationController pushViewController:userprofile animated:YES];
}

會話列表部分

接下來,我們一起來看看會話列表的實現,同樣的,我們也是創建一個類并繼承于EaseConversationListViewController

WX20170615-101039@2x.png

廢話不多說,上Code,在MessageViewController.m中
在ViewDidLoad中,我們加入如下代碼:

//首次進入刷新數據,加載會話列表
    [self tableViewDidTriggerHeaderRefresh];
  
    [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];
//獲取當前所有會話
    self.datalistArray = (NSMutableArray *) [[EMClient sharedClient].chatManager getAllConversations];
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [self tableViewDidTriggerHeaderRefresh];
    [self refreshAndSortView];
    
    self.datalistArray = (NSMutableArray *) [[EMClient sharedClient].chatManager getAllConversations];  //獲取當前所有會話
    [_messageTableView reloadData];
}

/**
 * 收到消息回調
 */
- (void)didReceiveMessages:(NSArray *)aMessages
{
    [self tableViewDidTriggerHeaderRefresh];
    [self refreshAndSortView]; //刷新內存中的消息
    //加載新的會話
    self.datalistArray = (NSMutableArray *) [[EMClient sharedClient].chatManager getAllConversations];
    //這里需要的話可以加入時間排序(別忘了刷新數據源)
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *funcIdentifier = @"funcIdentifier";
    
    if (indexPath.section == 0) {
        MsgFuncTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:funcIdentifier];
        if (!cell) {
            cell = [[MsgFuncTableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:funcIdentifier];
        }
        UIView *lineView = [UIView new];
        lineView.backgroundColor = [UIColor colorWithNumber:kLineColor];
        [cell addSubview:lineView];
        [lineView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.bottom.equalTo(cell);
            make.height.equalTo(@0.7);
        }];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        
        cell.imageV.image = [UIImage imageNamed:[NSString stringWithFormat:@"%@",[_funcArray objectAtIndex:0][indexPath.row]]];
        cell.label.text = [_funcArray objectAtIndex:1][indexPath.row];
        
        return cell;
    }
    else if (indexPath.section == 1) {
//        MessageChatTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
//        if (!cell) {
//            cell = [[MessageChatTableViewCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:cellIdentifier];
//        }
// 這里開始我們使用環信提供的一種cell
        EaseConversationCell * cell = [tableView dequeueReusableCellWithIdentifier:@"reuseID"];
        if (!cell) {
            cell = [[EaseConversationCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:@"reuseID"];
        }
        EMConversation *conversation = [_datalistArray objectAtIndex:indexPath.row];
        //    EMConversationTypeChat  = 0,  單聊會話
        //    EMConversationTypeGroupChat,  群聊會話
        //    EMConversationTypeChatRoom    聊天室會話
        switch (conversation.type) {
                //單聊會話
            case EMConversationTypeChat:
            {
                
                //這里有個小坑,剛開始不知道怎么獲取到對方的昵稱,就用了下面的方法去獲取,根據當前的會話是接收方還是發送方來獲取發送的對象,或接收的對象,結果有些能獲取到,有些返回的Null,
                //            cell.textLabel.text = [conversation lastReceivedMessage].direction == EMMessageDirectionSend? [conversation lastReceivedMessage].to : [conversation lastReceivedMessage].from;
                cell.titleLabel.text = conversation.conversationId;
                NSLog(@"發送方%@------接收方%@",[conversation lastReceivedMessage].from,[conversation lastReceivedMessage].to);
                //頭像,我這里用固定的頭像
                cell.avatarView.image = [UIImage imageNamed:kDefaultUserHeadImage];
                //設置頭像圓角
                cell.avatarView.imageCornerRadius = 20;
                
                //是否顯示角標
                cell.avatarView.showBadge = YES;
                //未讀消息數量
                cell.avatarView.badge = conversation.unreadMessagesCount;
                
                break;
            }
            default:
                break;
        }

        //這里是將會話的最后一條消息裝換成具體內容展示
        cell.detailLabel.text = [self subTitleMessageByConversation:conversation];
        //顯示最后一條消息的時間
        cell.timeLabel.text = [NSString stringWithFormat:@"%@",[self lastMessageDateByConversation:conversation]];
        
        //添加分割線
        UIView *lineView = [UIView new];
        lineView.backgroundColor = [UIColor colorWithNumber:kLineColor];
        [cell addSubview:lineView];
        [lineView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.bottom.equalTo(cell);
            make.height.equalTo(@0.7);
        }];
        
        return cell;
    }
    else {
        return [UITableViewCell new];
    }
}

在UITableView的didSelect中,代碼如下:

EMConversation *msgConversation = _datalistArray[indexPath.row];
        ChatViewController *chatVC = [[ChatViewController alloc] initWithConversationChatter:msgConversation.conversationId conversationType:EMConversationTypeChat];
        chatVC.hidesBottomBarWhenPushed = YES;
        chatVC.title = msgConversation.conversationId;
        [self.navigationController pushViewController:chatVC animated:YES];

接下來就是獲取最后消息的文字或者類型,以及獲得最后一條消息顯示的時間

//得到最后消息文字或者類型
-(NSString *)subTitleMessageByConversation:(EMConversation *)conversation
{
    NSString *ret = @"";
    EMMessage *lastMessage = [conversation latestMessage];
    EMMessageBody * messageBody = lastMessage.body;
    if (lastMessage) {
        EMMessageBodyType  messageBodytype = lastMessage.body.type;
        switch (messageBodytype) {

                //                 EMMessageBodyTypeText   = 1,    /*! \~chinese 文本類型 \~english Text */
                //                EMMessageBodyTypeImage,         /*! \~chinese 圖片類型 \~english Image */
                //                EMMessageBodyTypeVideo,         /*! \~chinese 視頻類型 \~english Video */
                //                EMMessageBodyTypeLocation,      /*! \~chinese 位置類型 \~english Location */
                //                EMMessageBodyTypeVoice,         /*! \~chinese 語音類型 \~english Voice */
                //                EMMessageBodyTypeFile,          /*! \~chinese 文件類型 \~english File */
                //                EMMessageBodyTypeCmd,           /*! \~chinese 命令類型 \~english Command */

                //圖像類型
            case EMMessageBodyTypeImage:
            {
                ret = NSLocalizedString(@"[圖片消息]", @"[image]");
            } break;
                //文本類型
            case EMMessageBodyTypeText:
            {
                NSString *didReceiveText = [EaseConvertToCommonEmoticonsHelper
                                            convertToSystemEmoticons:((EMTextMessageBody *)messageBody).text];  //表情映射
                ret = didReceiveText;
            } break;
                //語音類型
            case EMMessageBodyTypeVoice:
            {
                ret = NSLocalizedString(@"[語音消息]", @"[voice]");
            } break;
                //位置類型
            case EMMessageBodyTypeLocation:
            {
                ret = NSLocalizedString(@"[地理位置信息]", @"[location]");
            } break;
                //視頻類型
            case EMMessageBodyTypeVideo:
            {
                ret = NSLocalizedString(@"[視頻消息]", @"[video]");
            } break;
                
            default:
                break;
        }
    }
    return ret;
}

//獲得最后一條消息顯示的時間
- (NSString *)lastMessageDateByConversation:(EMConversation *)conversation {
  
    NSString *latestMessageTime = @"";
    EMMessage *lastMessage = [conversation latestMessage];;
    if (lastMessage) {
        latestMessageTime = [NSDate formattedTimeFromTimeInterval:lastMessage.timestamp];
    }
    
    return latestMessageTime;
}

//給加載會話列表添加下拉刷新方法
- (void)tableViewDidTriggerHeaderRefresh {
    [super tableViewDidTriggerHeaderRefresh]; //這里必須寫super,完全繼承
    
    __weak MessageViewController *weakSelf = self;
    self.messageTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        [weakSelf.messageTableView reloadData];
        [weakSelf tableViewDidFinishTriggerHeader:YES reload:NO];
        //        [weakSelf.messageTableView reloadData]; //刷新數據源
        //        [weakSelf refreshAndSortView]; //刷新內存頁面
        [weakSelf.messageTableView.mj_header endRefreshing]; //結束刷新
    }];
    self.messageTableView.mj_header.accessibilityIdentifier = @"refresh_header";
    //            header.updatedTimeHidden = YES;
}

截止到這里基本上就已經完成簡單的單聊了,至于添加好友聯系人列表都比較簡單,大家可以到環信官網中自己查看,以后有時間的話會補上群組,聊天室這一塊的,最后補上兩條不錯的文章,大家有相關需求的話可以去看看**
基于環信實現發送/預覽文件的功能
基于環信實現實時視頻語音通話功能

結束語:本次簡單集成環信就算完成了,希望大家能多多指教,多提寶貴意見,有什么不足的地方可以在文章下方留言,希望這篇文章能真正的幫助到大家,如果您覺得還算不錯的話,請點贊或打賞!謝謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容