 集成环信IM(单聊)

简单总结一下,仅对日后再次集成更加快速便捷,多数是对文档的简单概括.

一.快速集成环信SDK

1 注册环信账号,创建新应用,获取应用的AppKey.
2 参照官方文档,导入SDK:
(1).下载官网SDK (http://www.easemob.com/download/im:), 从官网上下载下来的包中分为如下五部分:
• 环信 iOS HyphenateSDK 开发使用(不包含实时通话功能)
• 环信 iOS HyphenateFullSDK 开发使用(包含实时通话功能)
• 环信 iOS doc SDK 相关API文档
• 环信 iOS ChatUIDemo3.0 工程源码
• 环信 iOS EaseUI 工程源码
• iOS chatdemo-ui-3.x.x.ipa 打包的 ipa
(2).将SDK文件夹(HyphenateSDK)拖入项目中, 并勾选上 Destination。
(其中,SDK 不支持 bitcode,向 Build Settings → Linking → Enable Bitcode 中设置 NO。)
添加以下库:

ios_import3.0sdk.jpg

3 增加预编译头文件pch,保证头文件的全局引用.( 【Build Settings】中搜索框输入prefix header 将【Precompile Prefix Header】设置为【YES】,将PCH文件地址填写进【Prefix Header】中

方法一:也可以通过将PCH文件直接拖入【Prefix Header】框中.
方法二: "$(SRCROOT)/当前工程名字/需要包含头文件所在文件夹").

特别注意(否则会报一堆错):
导入<UIKit/UIKit.h>等OC框架时需要添加:
# ifdef OBJC
# endif

以上步骤经过command + b 编译没错,即代表导入环信SDK成功.

二.搭建界面实现单聊功能

初始化SDK:
在AppDelegate.m加入如下代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//AppKey:注册的AppKey,详细见下面注释。

//apnsCertName:推送证书名(不需要加后缀),详细见下面注释。

   EMOptions *options = [EMOptions     optionsWithAppkey:@"douser#istore"];
options.apnsCertName = @"istore_dev";
[[EMClient sharedClient] initializeSDKWithOptions:options];

return YES;
}
    // APP进入后台
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[EMClient sharedClient] applicationDidEnterBackground:application];
}

    // APP将要从后台返回
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[EMClient sharedClient] applicationWillEnterForeground:application];
}

简单的搭了一个界面:

屏幕快照 2016-08-22 12.45.03.png

按照文档加入以下代码:
注册事件:
- (IBAction)hdRegister:(id)sender {
//其中@"hd222"是自己设置的用户名
EMError *error = [[EMClient sharedClient] registerWithUsername:@"hd222" password:@"111111"];
if (error==nil) {
NSLog(@"注册成功");
}
}

登录事件:

- (IBAction)login:(id)sender {
//其中@"hd222"是自己设置的用户名
EMError *error = [[EMClient sharedClient] loginWithUsername:@"hd222" password:@"111111"];
if (!error) {
    NSLog(@"登录成功");
}
}

退出事件:
- (IBAction)goOut:(id)sender {
EMError *error = [[EMClient sharedClient] logout:YES];
if (!error) {
NSLog(@"退出成功");
}
}

发送消息:
- (IBAction)sendMessage:(id)sender {

EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithText:@"要发送的消息"];
NSString *from = [[EMClient sharedClient] currentUsername];

//生成Message, 其中@"hd111"是自己设置的消息的接收方
EMMessage *message = [[EMMessage alloc] initWithConversationID:@"6001" from:from to:@"hd111" body:body ext:nil];
message.chatType = EMChatTypeChat;// 设置为单聊消息
//message.chatType = EMChatTypeGroupChat;// 设置为群聊消息
//message.chatType = EMChatTypeChatRoom;// 设置为聊天室消息

//发送Message
[[EMClient sharedClient].chatManager asyncSendMessage:message progress:nil completion:^(EMMessage *aMessage, EMError *aError) {}]; 
}
注意:

发送消息过程中,需要在viewDidLoad方法中添加EMChatManagerDelegate协议的代理.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[EMClient sharedClient].chatManager addDelegate:self delegateQueue:nil];
}
一定不要忘了遵守协议:

屏幕快照 2016-08-22 13.06.17.png

调用接收消息的代理方法(直接复制粘贴即可):
#pragma mark -接收消息的代理方法
- (void)didReceiveMessages:(NSArray *)aMessages{

for (EMMessage *message in aMessages) {
    EMMessageBody *msgBody = message.body;
    switch (msgBody.type) {
        case EMMessageBodyTypeText:
        {
            // 收到的文字消息
            EMTextMessageBody *textBody = (EMTextMessageBody *)msgBody;
            NSString *txt = textBody.text;
            NSLog(@"收到的文字是 txt -- %@",txt);
        }
            break;
        case EMMessageBodyTypeImage:
        {
            // 得到一个图片消息body
            EMImageMessageBody *body = ((EMImageMessageBody *)msgBody);
            NSLog(@"大图remote路径 -- %@"   ,body.remotePath);
            NSLog(@"大图local路径 -- %@"    ,body.localPath); // // 需要使用sdk提供的下载方法后才会存在
            NSLog(@"大图的secret -- %@"    ,body.secretKey);
            NSLog(@"大图的W -- %f ,大图的H -- %f",body.size.width,body.size.height);
            NSLog(@"大图的下载状态 -- %lu",body.downloadStatus);
            
            
            // 缩略图sdk会自动下载
            NSLog(@"小图remote路径 -- %@"   ,body.thumbnailRemotePath);
            NSLog(@"小图local路径 -- %@"    ,body.thumbnailLocalPath);
            NSLog(@"小图的secret -- %@"    ,body.thumbnailSecretKey);
            NSLog(@"小图的W -- %f ,大图的H -- %f",body.thumbnailSize.width,body.thumbnailSize.height);
            NSLog(@"小图的下载状态 -- %lu",body.thumbnailDownloadStatus);
        }
            break;
        case EMMessageBodyTypeLocation:
        {
            EMLocationMessageBody *body = (EMLocationMessageBody *)msgBody;
            NSLog(@"纬度-- %f",body.latitude);
            NSLog(@"经度-- %f",body.longitude);
            NSLog(@"地址-- %@",body.address);
        }
            break;
        case EMMessageBodyTypeVoice:
        {
            // 音频sdk会自动下载
            EMVoiceMessageBody *body = (EMVoiceMessageBody *)msgBody;
            NSLog(@"音频remote路径 -- %@"      ,body.remotePath);
            NSLog(@"音频local路径 -- %@"       ,body.localPath); // 需要使用sdk提供的下载方法后才会存在(音频会自动调用)
            NSLog(@"音频的secret -- %@"        ,body.secretKey);
            NSLog(@"音频文件大小 -- %lld"       ,body.fileLength);
            NSLog(@"音频文件的下载状态 -- %lu"   ,body.downloadStatus);
            NSLog(@"音频的时间长度 -- %lu"      ,body.duration);
        }
            break;
        case EMMessageBodyTypeVideo:
        {
            EMVideoMessageBody *body = (EMVideoMessageBody *)msgBody;
            
            NSLog(@"视频remote路径 -- %@"      ,body.remotePath);
            NSLog(@"视频local路径 -- %@"       ,body.localPath); // 需要使用sdk提供的下载方法后才会存在
            NSLog(@"视频的secret -- %@"        ,body.secretKey);
            NSLog(@"视频文件大小 -- %lld"       ,body.fileLength);
            NSLog(@"视频文件的下载状态 -- %lu"   ,body.downloadStatus);
            NSLog(@"视频的时间长度 -- %lu"      ,body.duration);
            NSLog(@"视频的W -- %f ,视频的H -- %f", body.thumbnailSize.width, body.thumbnailSize.height);
            
            // 缩略图sdk会自动下载
            NSLog(@"缩略图的remote路径 -- %@"     ,body.thumbnailRemotePath);
            NSLog(@"缩略图的local路径 -- %@"      ,body.thumbnailLocalPath);
            NSLog(@"缩略图的secret -- %@"        ,body.thumbnailSecretKey);
            NSLog(@"缩略图的下载状态 -- %lu"      ,body.thumbnailDownloadStatus);
        }
            break;
        case EMMessageBodyTypeFile:
        {
            EMFileMessageBody *body = (EMFileMessageBody *)msgBody;
            NSLog(@"文件remote路径 -- %@"      ,body.remotePath);
            NSLog(@"文件local路径 -- %@"       ,body.localPath); // 需要使用sdk提供的下载方法后才会存在
            NSLog(@"文件的secret -- %@"        ,body.secretKey);
            NSLog(@"文件文件大小 -- %lld"       ,body.fileLength);
            NSLog(@"文件文件的下载状态 -- %lu"   ,body.downloadStatus);
        }
            break;
            
        default:
            break;
    }
}
}

可以利用真机和模拟器之间发送消息测试,以上是简单的发送消息

单聊:
打开单聊界面按钮集成了基于EaseUI的单聊界面(建议当对界面要求不高的时候直接使用环信UI):

集成方式:
直接将EaseUI拖入已经集成SDK的项目中,然后进行初始化:
第 1 步:引入相关头文件 #import “EaseUI.h”。
第 2 步:在工程的 AppDelegate 中的以下方法中,调用 EaseUI 对应方法。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[EaseSDKHelper shareHelper] easemobApplication:application
didFinishLaunchingWithOptions:launchOptions
appkey:@"douser#istore"
apnsCertName:@"istore_dev"
otherConfig:@{kSDKConfigEnableConsoleLogger:[NSNumber numberWithBool:YES]}];
return YES;
}

直接创建聊天会话:
//打开单聊界面
- (IBAction)openChat:(id)sender {

//hd111是注册在真机上的账号  用于真机和模拟器实现聊天
EaseMessageViewController *chatController = [[EaseMessageViewController alloc] initWithConversationChatter:@"hd111" conversationType:EMConversationTypeChat];

chatController.title = @"环信单聊";

    //    UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:chatController];

[self.navigationController pushViewController:chatController animated:YES];
    }
屏幕快照 2016-08-22 13.09.56.png

点击单聊界面按钮利用真机和模拟器即测试实现单聊功能.

swift代码:(处理头像)

import UIKit

class HuanXinChatViewController: EaseMessageViewController {

var blackArr:NSArray!

override init!(conversationChatter: String!, conversationType: EMConversationType) {
    super.init(conversationChatter: conversationChatter, conversationType: conversationType)
}

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    super.init(nibName: nil, bundle: nil)
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

override func viewWillDisappear(_ animated: Bool) {
    NotificationCenter.default.post(name: NSNotification.Name(rawValue: KRemoveMessageAlterNotification), object: nil)
}

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.delegate = self
    self.dataSource = self
    
    self.chatBarMoreView.removeItematIndex(3)
    self.chatBarMoreView.removeItematIndex(3)
    //        EMAlbum
    self.chatBarMoreView.updateItem(with: UIImage(named:"EMAlbum"), highlightedImage: UIImage(named:"EMAlbumClick"), title: "", at: 0)
    self.chatBarMoreView.updateItem(with: UIImage(named:"EMLocation"), highlightedImage: UIImage(named:"EMLocationClick"), title: "", at: 1)
    self.chatBarMoreView.updateItem(with: UIImage(named:"EMTakePhotos"), highlightedImage: UIImage(named:"EMTakePhotosClick"), title: "", at: 2)
    
    IQKeyboardManager.sharedManager().enableAutoToolbar = false
    
    //设置自己头像
    if UserDefaults.standard.value(forKey: "headpath") == nil{
        getUserHeadPic()
    }
    //        print("打印头像路径\(UserDefaults.standard.value(forKey: "headpath"))")
    getBlackList()
}

//获取头像
func  getUserHeadPic(){
    NetworkTool.GET(URLString: GETUSERINFO, parameters: nil, token: true, success: { (response) in
        if let headPic = response["headpath"]{
            UserDefaults.standard.setValue("\(OSS)\(headPic!)", forKey: "headpath")
        }
    }, failure: { (error) in
        
    })
}

//黑名单
func getBlackList(){
    
    var error:EMError? = nil
    blackArr = EMClient.shared().contactManager.getBlackListFromServerWithError(&error) as NSArray!
    
//        if blackList?.count == 0{
//
//        }else{
//           
//        }
    
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}
}

extension HuanXinChatViewController:EaseMessageViewControllerDataSource,EaseMessageViewControllerDelegate{

//不显示已读
func messageViewControllerShouldMarkMessages(asRead viewController: EaseMessageViewController!) -> Bool {
    return true
}

//    func messageViewController(_ viewController: EaseMessageViewController!, shouldSendHasReadAckFor message: EMMessage!, read: Bool) -> Bool {
//        return false
//    }

func messageViewController(_ viewController: EaseMessageViewController!, modelFor message: EMMessage!) -> IMessageModel! {
    
    let model = EaseMessageModel.init(message: message)
    
    let currentName = EMClient.shared().currentUsername
    
    let messageName = model?.message.from
    
    let messageTo = model?.message.to
    
    
    print("打印环信消息的拓展\(model?.messageStatus)")
    
    if model?.messageStatus == EMMessageStatusFailed{
         print("信息发送失败或已被屏蔽")
//            Alert.showBottomWithText(text: "信息发送失败或已被屏蔽", bottomOffset: 200, duration: 5)
//            
    }
    //        print("打印环信消息的拓展\(messageTo)")
    
    if currentName! == messageName {
        
        if blackArr.contains(currentName!) {
//                Alert.showText(text: "信息发送失败或已被屏蔽")
            print("信息发送失败或已被屏蔽")
        }
        
        model?.avatarURLPath = UserDefaults.standard.value(forKey: "headpath") as! String!
        model?.nickname = UserDefaults.standard.value(forKey: "userName") as! String!
        model?.failImageName = "icon_avatar"
    }else{
        //            print("打印环信消息的拓展\(messageName)")
        if message.ext != nil{
            //                print("打印拓展消息的字典\((message.ext as Dictionary)["userName"])")
            if (message.ext as Dictionary)["userName"] != nil{
                model?.nickname = (message.ext as? Dictionary)?["userName"]
            }
            model?.avatarURLPath = (message.ext as? Dictionary)?["userPic"]
            model?.failImageName = "icon_avatar"
        }
    }
    //        http://oss.hp.etong.tech/images/ed2cf47424a55b2a213316b8ab68b535.png
    return model
}
//点击头像
func messageViewController(_ viewController: EaseMessageViewController!, didSelectAvatarMessageModel messageModel: IMessageModel!) {
    //        let userProfile = UserProfileViewController()
    print("点击头像啦")
}
}

环信获取消息的未读数量

//获取消息的未读数量
func getUnReadNum() -> Int {
    
    var unReadObj = 0
    if UserDefaults.standard.value(forKey: "token") != nil {
        if EMClient.shared().isLoggedIn == true{
            if EMClient.shared().chatManager != nil{
                let conversations: NSArray? = EMClient.shared().chatManager.getAllConversations() as NSArray?
                if (conversations != nil && conversations!.count > 0) {
                    for  conversation: EMConversation in conversations as! [EMConversation] {
                        unReadObj += Int(conversation.unreadMessagesCount)
                    }
                }
            }
        }
    }
    return unReadObj
}

实时收到消息的代理:

//环信聊天代理
extension AppDelegate:EMChatManagerDelegate{
func messagesDidReceive(_ aMessages: [Any]!) {
    //收到新消息后 提示音
    playSoundEffect("sound.caf")
    
    if aMessages.count > 0 {
        
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: KReceivedNewMessageNotification), object: nil)
    }
    for message in aMessages {
        let messageMo = message as! EMMessage
        let msgBody = messageMo.body
        if msgBody?.type == EMMessageBodyTypeText{
            let textBody = msgBody as! EMTextMessageBody
            messageDetail = textBody.text!
        }else if msgBody?.type == EMMessageBodyTypeImage{
            messageDetail = "[图片]"
        }else if msgBody?.type == EMMessageBodyTypeLocation{
            messageDetail = "[位置]"
        }else if msgBody?.type == EMMessageBodyTypeVoice{
            messageDetail = "[语音消息]"
        }else if msgBody?.type == EMMessageBodyTypeVideo{
            messageDetail = "[视频]"
        }
    }
}
}

音效

/** 收到新消息播放音效文件 */
func playSoundEffect(_ name: String) {
    // 获取音效
    let audioFile: String? = Bundle.main.path(forResource: name, ofType: nil)
    let fileUrl = URL(fileURLWithPath: audioFile!)
    // 1、获得系统声音ID
    var soundID: SystemSoundID = 0
    AudioServicesCreateSystemSoundID(((fileUrl as CFURL) ), &soundID)
    // 2、播放音频
    AudioServicesPlaySystemSound(soundID)
    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容