集成環(huán)信3.0,入坑指南,看這一篇就夠

版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。

前言

公司最近要求做即時(shí)通訊, 直接用了三方環(huán)信了,今天和大家談?wù)勱P(guān)于 我做環(huán)信集成的過(guò)程和坑點(diǎn),有什么不足的地方,還望大家多多指正

與環(huán)信V2.0的區(qū)別

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

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

準(zhǔn)備工作

  • 注冊(cè)環(huán)信開(kāi)發(fā)者賬號(hào)并創(chuàng)建后臺(tái)應(yīng)用
  • 制作并上傳推送證書來(lái)實(shí)現(xiàn)離線推送功能
  • 導(dǎo)入SDK,這里推薦使用CocoaPods進(jìn)行導(dǎo)入,其中有兩個(gè)版本供大家選擇:HyphenateLiteHyphenate 其中后者包含了實(shí)時(shí)語(yǔ)音
    這里我們就不過(guò)多闡述了,在這里附上官方的SDK集成網(wǎng)址供大家參考
    集成iOS SDK前準(zhǔn)備工作
    iOS的SDK導(dǎo)入

初始化SDK,以及登錄,注冊(cè),自動(dòng)登錄,退出登錄

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

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

在登錄頁(yè)面LoginViewController.m中:

//因?yàn)樵O(shè)置了自動(dòng)登錄模式,所以登錄之前要注銷之前的用戶,否則重復(fù)登錄會(huì)拋出異常
  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:^{
           // 設(shè)置自動(dòng)登錄
            [EMClient sharedClient].options.isAutoLogin = YES;

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

在注冊(cè)頁(yè)面RegisterViewController.m中:

//如果注冊(cè)不成功,需要去環(huán)信官網(wǎng)切換注冊(cè)模式為開(kāi)放注冊(cè),而不是授權(quán)注冊(cè)
    EMError *error = [[EMClient sharedClient] registerWithUsername:_userTextField.text password:_passTextField.text];
    if (error == nil) {
        NSLog(@"注冊(cè)成功");
        kSetLogin(YES);
//這里是注冊(cè)的時(shí)候在調(diào)用登錄方法, 讓其登錄一次,只有這樣下次才能自動(dòng)登錄,只設(shè)置自動(dòng)登錄的Boll值是不行的 
//也就是說(shuō)這里的邏輯是一旦讓用戶注冊(cè),如果注冊(cè)成功直接跳轉(zhuǎn)到我的頁(yè)面,并設(shè)置下次自動(dòng)登錄,并不是注冊(cè)完成后回到登錄頁(yè)面
        [[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(@"注冊(cè)失敗%d",error.code);
    }

設(shè)置自動(dòng)登錄的代理,以及實(shí)現(xiàn)邏輯,在AppDelegate.m中:

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

需要注意的是:添加代理一定不要忘了移除代理,這個(gè)暫且算一個(gè)小小的注意點(diǎn)

//移除代理, 因?yàn)檫@里是多播機(jī)制
- (void)dealloc {
    [[EMClient sharedClient] removeDelegate:self];
}


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


/**
 環(huán)信 監(jiān)聽(tīng)網(wǎng)絡(luò)狀態(tài)(重連)
 1.登錄成功后,手機(jī)無(wú)法上網(wǎng)時(shí)
 2.登錄成功后,網(wǎng)絡(luò)狀態(tài)變化時(shí)
 aConnectionState:當(dāng)前狀態(tài)
 */
- (void)didConnectionStateChanged:(EMConnectionState)aConnectionState{
    if (aConnectionState == EMConnectionConnected) {
        NSLog(@"網(wǎng)絡(luò)連接成功");
    }else{
        NSLog(@"網(wǎng)絡(luò)斷開(kāi)");
        //監(jiān)聽(tīng)網(wǎng)絡(luò)狀態(tài)(這里通知的目地是檢測(cè)到如果沒(méi)網(wǎng)絡(luò)的情況下,修改Navigation.title的值)
        [[NSNotificationCenter defaultCenter] postNotificationName:
        AFNetworkingReachabilityDidChangeNotification object:nil];
    }
}

/*!
 *  重連
 *  有以下幾種情況,會(huì)引起該方法的調(diào)用:
 *  1. 登錄成功后,手機(jī)無(wú)法上網(wǎng)時(shí),會(huì)調(diào)用該回調(diào)
 *  2. 登錄成功后,網(wǎng)絡(luò)狀態(tài)變化時(shí),會(huì)調(diào)用該回調(diào)
 */
- (void)connectionStateDidChange:(EMConnectionState)aConnectionState{
    NSLog(@"斷線重連不需要其他操作%u",aConnectionState);
}


//APP進(jìn)入后臺(tái)
- (void)applicationDidEnterBackground:(UIApplication *)application {
    [[EMClient sharedClient] applicationDidEnterBackground:application];
}

//APP將要從后臺(tái)返回
- (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(@"退出環(huán)信登錄成功");
                    }else{
                        NSLog(@"退出環(huán)信登錄失敗,%u",aError.code);
                    }
                }];
                
                [self.navigationController popViewControllerAnimated:YES];
            }];
        } actionBTitle:@"取消" actionBHandler:nil totalCompletion:nil];
    } else {
        [custom showAlertView:@"您尚未登錄" byView:self.view completion:nil];
    }
}

進(jìn)行到這里以后,相信大家就能實(shí)現(xiàn)簡(jiǎn)單的登錄,注冊(cè)以及自動(dòng)登錄了,是不是也比較簡(jiǎn)單呢,接下來(lái)簡(jiǎn)單說(shuō)一下在登錄,注冊(cè)過(guò)程中遇到的問(wèn)題。

  1. 引用頭文件的時(shí)候報(bào)錯(cuò)出現(xiàn):Hyphenate/EMSDK.h’ file no found
    解決方法: 換下引用#import <HyphenateLite/HyphenateLite.h>
    或者#import <Hyphenate/Hyphenate.h>

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

    iOS的SDK導(dǎo)入中有詳細(xì)地說(shuō)明,拿實(shí)時(shí)音視頻版本版本為例 : 執(zhí)行完以上命令如圖所示
    運(yùn)行完畢后得到的Hyphenate.framework就是最后的結(jié)果,拖進(jìn)工程,編譯打包上架。
    注意 : 最后得到的包必須真機(jī)編譯運(yùn)行,并且工程要設(shè)置編譯二進(jìn)制文件General->Embedded Bunaries.
    刪除i386、x86_64平臺(tái)后,SDK會(huì)無(wú)法支持模擬器編譯,只需要在上傳AppStore時(shí)在進(jìn)行刪除,上傳后,替換為刪除前的SDK,建議先分別把i386、x86_64、arm64、armv7各平臺(tái)的包拆分到本地,上傳App Store時(shí)合并arm64、armv7平臺(tái),并移入Hyphenate.framework內(nèi)。上傳后,重新把各平臺(tái)包合并移入動(dòng)態(tài)庫(kù)
  4. 依舊是打包錯(cuò)誤: 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,或者整個(gè)info.plist刪掉

接下來(lái)我們說(shuō)一下,會(huì)話聊天部分和會(huì)話列表的兩個(gè)部分
這里用到的是EaseUI ,它封裝了 IM 功能常用的控件(如聊天會(huì)話、會(huì)話列表、聯(lián)系人列表)

集成EaseUI

請(qǐng)戳這里查看 → EaseUI使用指南
在這里集成EaseUI的時(shí)候,有兩種方法:

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

我們先來(lái)看看使用第一種方法集成時(shí)候的過(guò)程和遇到的坑點(diǎn):

坑點(diǎn)1: 使用cocoaPods時(shí)候,出現(xiàn)了報(bào)錯(cuò)的信息,發(fā)現(xiàn)無(wú)法將環(huán)信的EaseUI導(dǎo)入。
這時(shí)候我們跟隨提示的指令進(jìn)行更新pods就可以了,主要是pod 問(wèn)題 本地倉(cāng)庫(kù)太舊了, 終端執(zhí)行pod repo update, 之后在pod search 'Hyphenate' 如果可以找到3.3.0版本, 就可以下載了 podfile 里面 platform 要指定8.0

在導(dǎo)入完成以后,在.pch文件中引用了#import <EaseUI/EaseUI.h>,編譯,恩,居然沒(méi)有報(bào)錯(cuò),看來(lái)可以進(jìn)行下一步了
直接在AppDelegate.m中初始化EaseUI:

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

這時(shí),當(dāng)我滿懷信心跑起來(lái)了工程,納尼??不能自動(dòng)登錄了,每次必須退出登錄以后,再登錄一次以后才能實(shí)現(xiàn)自動(dòng)登錄,然后當(dāng)我第二次運(yùn)行工程的時(shí)候發(fā)現(xiàn)自動(dòng)登錄又失效了,什么鬼?!

2C83F9DCCF6D31A0960BCCC31D46A933.jpg

坑點(diǎn)2: 直接登錄不能發(fā)送消息, 必須自動(dòng)登錄以后才能發(fā)送接收,自動(dòng)登錄大部分時(shí)候會(huì)走失敗的回調(diào)
最后依靠萬(wàn)能的環(huán)信客服人員提供了技術(shù)支持,不得不說(shuō)環(huán)信的客服還是很給力的
WX20170614-165444@2x.png

原來(lái)是使用pods導(dǎo)入了兩個(gè)版本的SDK,使用pods導(dǎo)入的同學(xué)們一定要注意這個(gè)問(wèn)題啊,不要重復(fù)導(dǎo)入,不然會(huì)出現(xiàn)許多未知的bug,
I27FF4PMBQQCP{PLIQ{5E4B.jpg

接下來(lái)我們看一下第二種方法:手動(dòng)導(dǎo)入EaseUI

  1. 首先我們根據(jù)下載好的環(huán)信demo中的文件拖入到工程中,

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

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


    WX20170614-171907@2x.png

這個(gè)是因?yàn)橛玫搅薝IKit里的類,但是只導(dǎo)入了Foundation框架,這個(gè)錯(cuò)誤在其他類里也會(huì)出現(xiàn),我們可以手動(dòng)修改Founfation為UIKit,但是我不建議這么做,第一這個(gè)做法的工程量比較大, 在其他類里面也要導(dǎo)入,二,不利于移植,當(dāng)以后環(huán)信更新的時(shí)候我們還是需要做同樣的操作,這里我的做法的創(chuàng)建一個(gè)pch文件,在pch文件里面導(dǎo)入U(xiǎn)IKit。
解決辦法:建一個(gè)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 --,不然可能會(huì)報(bào)NSObjcRunTime的錯(cuò)誤

4.環(huán)信內(nèi)部集成的MBProgressHUD SDWebImage MJRefresh 與我們工程中集成的這幾個(gè)第三方庫(kù)發(fā)生沖突!
解決方法:刪掉工程中自己集成的這些第三方庫(kù),或者刪除環(huán)信EaseUI 里面的這些第三方庫(kù)!
需要注意的是:如果刪除的是環(huán)信集成的第三方庫(kù)!由于環(huán)信在集成的第三方庫(kù)中加了EM前綴! 記得刪掉EaseUI 中使用方法的前綴,不然會(huì)報(bào)錯(cuò)!

如果集成的是不包含實(shí)時(shí)音視頻的SDK , 手動(dòng)導(dǎo)入EaseUI的話 , 那么此時(shí)還會(huì)報(bào)Hyphenate/EMSDK.h’ file no found
這時(shí)需要把 #import <Hyphenate/Hyphenate.h>注釋掉,然后把報(bào)錯(cuò)地方的Hyphenate換成HyphenateLite就可以了,和上面提到的第一點(diǎn)是一樣的
到這里以后,應(yīng)該沒(méi)有什么問(wèn)題,編譯如果成功的話,那么恭喜你了

至此,我們就導(dǎo)入了EaseUI并在appDelegate.m中初始化了EaseUI,接下來(lái)我們就先來(lái)完善聊天的頁(yè)面

聊天頁(yè)面部分

EaseUI集成應(yīng)用其實(shí)簡(jiǎn)單很多很多,里面也封裝了關(guān)于頭像昵稱的設(shè)置,所需要做的只是把代理方法實(shí)現(xiàn),前提是你的聊天頁(yè)面等都是繼承EaseUI里面的相關(guān)的類去做的。

這里給大家推薦環(huán)信官方論壇的一個(gè)快速集成聊天的網(wǎng)址:IOS快速集成環(huán)信IM - 基于官方的Demo優(yōu)化,5分鐘集成環(huán)信IM功能

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

由于環(huán)信官方只是通過(guò)用戶名的id進(jìn)行會(huì)話,所以不是好友也可以進(jìn)行聊天,我們先做一個(gè)簡(jiǎn)單的單聊頁(yè)面,如圖 (PS:用戶頭像環(huán)信并不進(jìn)行存儲(chǔ),所以我們后期實(shí)現(xiàn)代理方法進(jìn)行處理就可以了)

首先我們創(chuàng)建一個(gè)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的方法中:我們修改環(huán)信的一些設(shè)置,讓他更符合我們的開(kāi)發(fā)需求

- (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]];
    
    //設(shè)置頭像圓角
    [[EaseBaseMessageCell appearance] setAvatarSize:40.f];
    [[EaseBaseMessageCell appearance] setAvatarCornerRadius:20.f];
    
    //隱藏對(duì)話時(shí)的昵稱
    [EaseBaseMessageCell appearance].messageNameIsHidden = YES;
    //修改字體高度,這樣在隱藏昵稱的時(shí)候,可以讓氣泡對(duì)齊
    [EaseBaseMessageCell appearance].messageNameHeight = 10;
    
    //修改發(fā)送圖片,定位,等的所在的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"]];
    
    //刪除功能模塊中的實(shí)時(shí)通話
    [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];
    
    //設(shè)置按住說(shuō)話的圖片數(shù)組
//    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];
    
    //通過(guò)會(huì)話管理者獲取已收發(fā)消息 (bug:會(huì)話列表已經(jīng)調(diào)用了刷新,如果繼續(xù)調(diào)用的話會(huì)出現(xiàn)消息重復(fù)的現(xiàn)象)
//    [self tableViewDidTriggerHeaderRefresh];
    
    //處理表情崩潰
    //    EaseEmotionManager *manager = [[EaseEmotionManager alloc] initWithType:(EMEmotionDefault) emotionRow:3 emotionCol:7 emotions:[EaseEmoji allEmoji]];
    //    [self.faceView setEmotionManagers:@[manager]];
    
    //語(yǔ)音動(dòng)態(tài)圖片數(shù)組
    /* 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];
}

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

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

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

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

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

在ChatViewController.m中,我們繼續(xù)添加:
注意:這里可能會(huì)出現(xiàn)發(fā)現(xiàn)重復(fù)消息。[self tableViewDidTriggerHeaderRefresh]; 檢查一下這個(gè)方法是不是在chatViewController 和EaseMessageViewCOntroller 的ViewDidLoad 里面都調(diào)用了,看如果都有,隨便刪除一個(gè)這個(gè)方法。就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"];
        }
    }
}

實(shí)現(xiàn)收到消息以后播放音頻以及震動(dòng)

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

根據(jù)遵循EaseMessageViewControllerDelegate的代理,實(shí)現(xiàn)長(zhǎng)按手勢(shì)的功能,轉(zhuǎn)發(fā),復(fù)制,刪除如下:

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

//觸發(fā)長(zhǎng)按手勢(shì)
- (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(@"復(fù)制", @"Copy") action:@selector(copyMenuAction:)];
    }
    
    if (_transpondMenuItem == nil) {
        _transpondMenuItem = [[UIMenuItem alloc] initWithTitle:NSLocalizedString(@"轉(zhuǎn)發(fā)", @"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;
}

添加表情,并發(fā)送,這里我并沒(méi)有遇到其他同學(xué)說(shuō)的表情發(fā)送崩潰的問(wèn)題,不過(guò)還是將解決方法貼出來(lái),在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;
}

//根據(jù)消息獲取表情信息
- (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;
}

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

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

最后就是實(shí)現(xiàn)ViewDidLoad中的通知了,這里的通知是刪除所有會(huì)話,以及對(duì)于實(shí)時(shí)語(yǔ)音的一些實(shí)現(xiàn),沒(méi)有這些需求的同學(xué)們可以略過(guò)

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

//當(dāng)前登錄賬號(hào)已經(jīng)被從服務(wù)器端刪除時(shí)會(huì)收到該回調(diào)
- (void)userAccountDidRemoveFromServer
{
    if ([self.imagePicker.mediaTypes count] > 0 && [[self.imagePicker.mediaTypes objectAtIndex:0] isEqualToString:(NSString *)kUTTypeMovie]) {
        [self.imagePicker stopVideoCapture];
    }
}

//服務(wù)被禁用
- (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]]) {
        //開(kāi)始call
        self.isViewDidAppear = NO;
    } else {
        //結(jié)束call
        self.isViewDidAppear = YES;
    }
}

截止到目前為止,聊天頁(yè)面基本上就差不多了,這里需要重點(diǎn)說(shuō)明的是聊天頁(yè)面頭像的數(shù)據(jù)處理

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

方法一:從APP服務(wù)器獲取昵稱和頭像
  • 昵稱和頭像的獲取:當(dāng)收到一條消息(群消息)時(shí),得到發(fā)送者的用戶ID,然后查找手機(jī)本地?cái)?shù)據(jù)庫(kù)是否有此用戶ID的昵稱和頭像,如沒(méi)有則調(diào)用APP服務(wù)器接口通過(guò)用戶ID查詢出昵稱和頭像,然后保存到本地?cái)?shù)據(jù)庫(kù)和緩存,下次此用戶發(fā)來(lái)信息即可直接查詢緩存或者本地?cái)?shù)據(jù)庫(kù),不需要再次向APP服務(wù)器發(fā)起請(qǐng)求。

  • 昵稱和頭像的更新:當(dāng)點(diǎn)擊發(fā)送者頭像時(shí)加載用戶詳情時(shí)從APP服務(wù)器查詢此用戶的具體信息然后更新本地?cái)?shù)據(jù)庫(kù)和緩存。當(dāng)用戶自己更新昵稱或頭像時(shí),也可以發(fā)送一條透?jìng)飨⒌狡渌脩艉陀脩羲诘娜海瑏?lái)更新該用戶的昵稱和頭像。

方法二:從消息擴(kuò)展中獲取昵稱和頭像
  • 昵稱和頭像的獲取:把用戶基本的昵稱和頭像的URL放到消息的擴(kuò)展中,通過(guò)消息傳遞給接收方,當(dāng)收到一條消息時(shí),則能通過(guò)消息的擴(kuò)展得到發(fā)送者的昵稱和頭像URL,然后保存到本地?cái)?shù)據(jù)庫(kù)和緩存。當(dāng)顯示昵稱和頭像時(shí),請(qǐng)從本地或者緩存中讀取,不要直接從消息中把賦值拿給界面(否則當(dāng)用戶昵稱改變后,同一個(gè)人會(huì)顯示不同的昵稱)。

  • 昵稱和頭像的更新:當(dāng)擴(kuò)展消息中的昵稱和頭像 URI 與當(dāng)前本地?cái)?shù)據(jù)庫(kù)和緩存中的相應(yīng)數(shù)據(jù)不同的時(shí)候,需要把新的昵稱保存到本地?cái)?shù)據(jù)庫(kù)和緩存,并下載新的頭像并保存到本地?cái)?shù)據(jù)庫(kù)和緩存。

這里我們選擇使用方案二,首先我們要實(shí)現(xiàn)存儲(chǔ)的功能,通過(guò)FMDB實(shí)現(xiàn)對(duì)用戶model的存儲(chǔ),這里大家可以根據(jù)自己的需求進(jìn)行存儲(chǔ)相關(guān)信息,在登錄成功之后你得先把自己的信息存儲(chǔ)起來(lái),在更改了個(gè)人資料之后,你要更新這里的存儲(chǔ)信息。這樣就可以做到更新頭像后歷史的頭像也會(huì)更新**

簡(jiǎn)單來(lái)說(shuō):流程是這樣的,存儲(chǔ)用戶的model信息 → 把用戶信息擴(kuò)展附加到要發(fā)送的消息中去 → 接收到消息以后通過(guò)數(shù)據(jù)源方法賦值到頭像上去

#pragma mark - EaseMessageViewControllerDataSource  
// 數(shù)據(jù)源方法  
- (id<IMessageModel>)messageViewController:(EaseMessageViewController *)viewController  
                           modelForMessage:(EMMessage *)message{  
      
    id<IMessageModel> model = nil;  
    // 根據(jù)聊天消息生成一個(gè)數(shù)據(jù)源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;  
    }  
    // 默認(rèn)頭像  
    //model.avatarImage = [UIImage imageNamed:@"EaseUIResource.bundle/user"];  
    //Placeholder image for network error  
    //項(xiàng)目圖片取出錯(cuò)誤的時(shí)候就用這張代替  
    model.failImageName = @"icon_Default-Avatar";  
    return model;  
}  

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

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

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

會(huì)話列表部分

接下來(lái),我們一起來(lái)看看會(huì)話列表的實(shí)現(xiàn),同樣的,我們也是創(chuàng)建一個(gè)類并繼承于EaseConversationListViewController

WX20170615-101039@2x.png

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

//首次進(jìn)入刷新數(shù)據(jù),加載會(huì)話列表
    [self tableViewDidTriggerHeaderRefresh];
  
    [[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];
//獲取當(dāng)前所有會(huì)話
    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];  //獲取當(dāng)前所有會(huì)話
    [_messageTableView reloadData];
}

/**
 * 收到消息回調(diào)
 */
- (void)didReceiveMessages:(NSArray *)aMessages
{
    [self tableViewDidTriggerHeaderRefresh];
    [self refreshAndSortView]; //刷新內(nèi)存中的消息
    //加載新的會(huì)話
    self.datalistArray = (NSMutableArray *) [[EMClient sharedClient].chatManager getAllConversations];
    //這里需要的話可以加入時(shí)間排序(別忘了刷新數(shù)據(jù)源)
}
- (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];
//        }
// 這里開(kāi)始我們使用環(huán)信提供的一種cell
        EaseConversationCell * cell = [tableView dequeueReusableCellWithIdentifier:@"reuseID"];
        if (!cell) {
            cell = [[EaseConversationCell alloc] initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:@"reuseID"];
        }
        EMConversation *conversation = [_datalistArray objectAtIndex:indexPath.row];
        //    EMConversationTypeChat  = 0,  單聊會(huì)話
        //    EMConversationTypeGroupChat,  群聊會(huì)話
        //    EMConversationTypeChatRoom    聊天室會(huì)話
        switch (conversation.type) {
                //單聊會(huì)話
            case EMConversationTypeChat:
            {
                
                //這里有個(gè)小坑,剛開(kāi)始不知道怎么獲取到對(duì)方的昵稱,就用了下面的方法去獲取,根據(jù)當(dāng)前的會(huì)話是接收方還是發(fā)送方來(lái)獲取發(fā)送的對(duì)象,或接收的對(duì)象,結(jié)果有些能獲取到,有些返回的Null,
                //            cell.textLabel.text = [conversation lastReceivedMessage].direction == EMMessageDirectionSend? [conversation lastReceivedMessage].to : [conversation lastReceivedMessage].from;
                cell.titleLabel.text = conversation.conversationId;
                NSLog(@"發(fā)送方%@------接收方%@",[conversation lastReceivedMessage].from,[conversation lastReceivedMessage].to);
                //頭像,我這里用固定的頭像
                cell.avatarView.image = [UIImage imageNamed:kDefaultUserHeadImage];
                //設(shè)置頭像圓角
                cell.avatarView.imageCornerRadius = 20;
                
                //是否顯示角標(biāo)
                cell.avatarView.showBadge = YES;
                //未讀消息數(shù)量
                cell.avatarView.badge = conversation.unreadMessagesCount;
                
                break;
            }
            default:
                break;
        }

        //這里是將會(huì)話的最后一條消息裝換成具體內(nèi)容展示
        cell.detailLabel.text = [self subTitleMessageByConversation:conversation];
        //顯示最后一條消息的時(shí)間
        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];

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

//得到最后消息文字或者類型
-(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 語(yǔ)音類型 \~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;
                //語(yǔ)音類型
            case EMMessageBodyTypeVoice:
            {
                ret = NSLocalizedString(@"[語(yǔ)音消息]", @"[voice]");
            } break;
                //位置類型
            case EMMessageBodyTypeLocation:
            {
                ret = NSLocalizedString(@"[地理位置信息]", @"[location]");
            } break;
                //視頻類型
            case EMMessageBodyTypeVideo:
            {
                ret = NSLocalizedString(@"[視頻消息]", @"[video]");
            } break;
                
            default:
                break;
        }
    }
    return ret;
}

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

//給加載會(huì)話列表添加下拉刷新方法
- (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]; //刷新數(shù)據(jù)源
        //        [weakSelf refreshAndSortView]; //刷新內(nèi)存頁(yè)面
        [weakSelf.messageTableView.mj_header endRefreshing]; //結(jié)束刷新
    }];
    self.messageTableView.mj_header.accessibilityIdentifier = @"refresh_header";
    //            header.updatedTimeHidden = YES;
}

截止到這里基本上就已經(jīng)完成簡(jiǎn)單的單聊了,至于添加好友聯(lián)系人列表都比較簡(jiǎn)單,大家可以到環(huán)信官網(wǎng)中自己查看,以后有時(shí)間的話會(huì)補(bǔ)上群組,聊天室這一塊的,最后補(bǔ)上兩條不錯(cuò)的文章,大家有相關(guān)需求的話可以去看看**
基于環(huán)信實(shí)現(xiàn)發(fā)送/預(yù)覽文件的功能
基于環(huán)信實(shí)現(xiàn)實(shí)時(shí)視頻語(yǔ)音通話功能

結(jié)束語(yǔ):本次簡(jiǎn)單集成環(huán)信就算完成了,希望大家能多多指教,多提寶貴意見(jiàn),有什么不足的地方可以在文章下方留言,希望這篇文章能真正的幫助到大家,如果您覺(jué)得還算不錯(cuò)的話,請(qǐng)點(diǎn)贊或打賞!謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容