1.在消息控制器获取历史回话记录
/**
* 获取历史会话对象记录
*/
- (void)loadConversations
{
// 当前登录用户回话对象列表
NSArray *conversations = [[EaseMob sharedInstance].chatManager conversations];
if (conversations.count == 0) {
// 从数据库conversation表获取
conversations = [[EaseMob sharedInstance].chatManager loadAllConversationsFromDatabaseWithAppend2Chat:YES];
}
self.conversations = conversations;
// 显示总的未读数
[self showTabBarBadge];
}
#pragma mark - EMChatManagerChatDelegate
/**
* 历史会话列表更新了会调用
*/
- (void)didUpdateConversationList:(NSArray *)conversationList
{
// 给数据源重新赋值
self.conversations = conversationList;
// 刷新表格
[self.tableView reloadData];
// 显示总的未读数
[self showTabBarBadge];
}
/**
* 未读消息数改变了会调用
*/
- (void)didUnreadMessagesCountChanged
{
[self.tableView reloadData];
// 显示总的未读数
[self showTabBarBadge];
}
/**
* 将总的未读消息数显示到tabBar上
*/
- (void)showTabBarBadge
{
NSInteger totalUnreadCount = 0;
for (EMConversation *conversation in self.conversations) {
totalUnreadCount += [conversation unreadMessagesCount];
}
self.navigationController.tabBarItem.badgeValue = [NSString stringWithFormat:@"%ld",totalUnreadCount];
}
2.消息控制器完整代码
//
// MessageViewController.m
#import "MessageViewController.h"
#import "EaseMob.h"
#import "ChatViewController.h"
@interface MessageViewController ()<EMChatManagerDelegate>
/**
* 历史会话对象列表
*/
@property(nonatomic,strong)NSArray *conversations;
@end
@implementation MessageViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 设置标题
self.title = @"消息";
// 添加聊天管理器的代理
[[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
// 获取历史回话记录
[self loadConversations];
}
- (void)dealloc
{
// 移除聊天管理器的代理
[[EaseMob sharedInstance].chatManager removeDelegate:self];
}
/**
* 获取历史会话对象记录
*/
- (void)loadConversations
{
// 当前登录用户回话对象列表
NSArray *conversations = [[EaseMob sharedInstance].chatManager conversations];
if (conversations.count == 0) {
// 从数据库conversation表获取
conversations = [[EaseMob sharedInstance].chatManager loadAllConversationsFromDatabaseWithAppend2Chat:YES];
}
self.conversations = conversations;
// 显示总的未读数
[self showTabBarBadge];
}
#pragma mark - EMChatManagerChatDelegate
/**
* 历史会话列表更新了会调用
*/
- (void)didUpdateConversationList:(NSArray *)conversationList
{
// 给数据源重新赋值
self.conversations = conversationList;
// 刷新表格
[self.tableView reloadData];
// 显示总的未读数
[self showTabBarBadge];
}
/**
* 未读消息数改变了会调用
*/
- (void)didUnreadMessagesCountChanged
{
[self.tableView reloadData];
// 显示总的未读数
[self showTabBarBadge];
}
/**
* 将总的未读消息数显示到tabBar上
*/
- (void)showTabBarBadge
{
NSInteger totalUnreadCount = 0;
for (EMConversation *conversation in self.conversations) {
totalUnreadCount += [conversation unreadMessagesCount];
}
self.navigationController.tabBarItem.badgeValue = [NSString stringWithFormat:@"%ld",totalUnreadCount];
}
/*!
@method
@brief 将要发起自动重连操作时发送该回调
@discussion
@result
*/
- (void)willAutoReconnect
{
self.title = @"网络连接中...";
}
/*!
@method
@brief 自动重连操作完成后的回调(成功的话,error为nil,失败的话,查看error的错误信息)
@discussion
@result
*/
- (void)didAutoReconnectFinishedWithError:(NSError *)error
{
if (!error) {
self.title = @"消息";
}
}
/**
* 监听网络状态
*/
- (void)didConnectionStateChanged:(EMConnectionState)connectionState{
// eEMConnectionConnected, //连接成功
// eEMConnectionDisconnected,//未连接
if (connectionState == eEMConnectionDisconnected) {
// NSLog(@"网络断开,未连接...");
self.title = @"未连接.";
}else{
// NSLog(@"网络通了...");
}
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.conversations.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 创建cell
static NSString *ID = @"messageCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 设置cell上显示的数据
EMConversation *conversation = self.conversations[indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:@"%@ 未读:%ld",conversation.chatter,conversation.unreadMessagesCount];
id body = conversation.latestMessage.messageBodies[0];
if ([body isKindOfClass:[EMTextMessageBody class]]) {
EMTextMessageBody *textBody = body;
cell.detailTextLabel.text = textBody.text;
}else if ([body isKindOfClass:[EMVideoMessageBody class]]){
EMVideoMessageBody *voiceBody = body;
cell.detailTextLabel.text = [voiceBody displayName];
}else if ([body isKindOfClass:[EMImageMessageBody class]]){
EMImageMessageBody *imgBody = body;
cell.detailTextLabel.text = imgBody.displayName;
}else{
cell.detailTextLabel.text = @"未知消息类型";
}
return cell;
}
#pragma mark - UITabelViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 从storyboard加载聊天控制器
ChatViewController *chatVc = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"chatPage"];
// 设置好友属性
EMConversation *conversation = self.conversations[indexPath.row];
EMBuddy *buddy = [EMBuddy buddyWithUsername:conversation.chatter];
chatVc.buddy = buddy;
// 跳转到聊天控制器
[self.navigationController pushViewController:chatVc animated:YES];
}
@end
3.从消息控制器跳转到聊天控制器
消息控制器中实现
#pragma mark - UITabelViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 从storyboard加载聊天控制器
ChatViewController *chatVc = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"chatPage"];
// 设置好友属性
EMConversation *conversation = self.conversations[indexPath.row];
EMBuddy *buddy = [EMBuddy buddyWithUsername:conversation.chatter];
chatVc.buddy = buddy;
// 跳转到聊天控制器
[self.navigationController pushViewController:chatVc animated:YES];
}
4.聊天控制器里需要标识消息为已读,聊天控制器完整代码
//
// ChatViewController.m
// 环信项目
//
// Created by yongkaidong on 16/2/13.
// Copyright © 2016年 com.yongkaidong. All rights reserved.
//
#import "ChatViewController.h"
#import "ChatCell.h"
#import "EaseMob.h"
#import "EMCDDeviceManager.h"
#import "AudioPlayTool.h"
#import "TimeCell.h"
#import "TimeTool.h"
@interface ChatViewController ()<UITableViewDataSource,UITableViewDelegate,UITextViewDelegate,EMChatManagerDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
/**
* inputToolbar(输入工具栏)底部的约束
*/
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputToolbarBottomConstraint;
/**
* inputToolbar高度的约束
*/
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputToolbarHeightConstraint;
@property (weak, nonatomic) IBOutlet UIButton *recordBtn;
@property (weak, nonatomic) IBOutlet UIButton *voiceBtn;
@property (weak, nonatomic) IBOutlet UITextView *textView;
/**
* 里边存的都是消息模型
*/
@property(nonatomic,strong)NSMutableArray *dataSources;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
/**
* 专门用来计算高度的cell工具对象
*/
@property(nonatomic,strong) ChatCell *chatCellTool;
/**
* 当前添加的时间字符串
*/
@property(nonatomic,copy)NSString *currentTimeStr;
/**
* 当前会话对象
*/
@property(nonatomic,strong)EMConversation *conversation;
@end
@implementation ChatViewController
- (NSMutableArray *)dataSources
{
if (!_dataSources) {
_dataSources = [NSMutableArray array];
}
return _dataSources;
}
/**
* 懒加载创建chatCellTool
*/
- (ChatCell *)chatCellTool
{
if (!_chatCellTool) {
//Identifier 用recivierCell或senderCell都可以,因为2个cell其实内部
//Identifier 声明在cell内部的
_chatCellTool = [self.tableView dequeueReusableCellWithIdentifier:senderCell];
}
return _chatCellTool;
}
- (void)viewDidLoad {
[super viewDidLoad];
// 0.设置标题
self.title = self.buddy.username;
// 1.监听键盘的弹出
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
// 2.监听键盘的退出
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
// 3.加载本地数据库的聊天记录
[self loadLocalChatRecords];
// 4.设置聊天管理器的代理
[[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
// 5.tableView滚动到最底部
[self scrollToBottom];
}
/**
* 加载本地的聊天记录
*/
- (void)loadLocalChatRecords
{
// 获取本地聊天记录 使用回话对象
EMConversation *conversation = [[EaseMob sharedInstance].chatManager conversationForChatter:self.buddy.username conversationType:eConversationTypeChat];
self.conversation = conversation;
// 加载与当前聊天用户的所有聊记录
NSArray *messages = [conversation loadAllMessages];
// 添加到数据源
for (EMMessage *msg in messages) {
[self addDataSourcesWithMessage:msg];
}
}
/**
* 键盘退出会调用
*/
- (void)keyboardWillHide:(NSNotification *)notice
{
// inputToolbar回到原位
self.inputToolbarBottomConstraint.constant = 0;
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
}];
}
/**
* 键盘弹出会调用
*/
- (void)keyboardWillShow:(NSNotification *)notice
{
// 1.获取键盘高度
CGFloat heigth = [notice.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
// 2.更改inputToolbar底部约束
self.inputToolbarBottomConstraint.constant = heigth;
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
}];
// 3.滚动tableView
[self scrollToBottom];
}
/**
* 添加通知要移除,我们可以在这里方法里移除
*/
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark -UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.dataSources.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//判断数据源类型
if ([self.dataSources[indexPath.row] isKindOfClass:[NSString class]]) {
TimeCell *cell = [tableView dequeueReusableCellWithIdentifier:@"timeCell"];
cell.timeLabel.text = self.dataSources[indexPath.row];
return cell;
}
// 1.获取消息模型
EMMessage *message = self.dataSources[indexPath.row];
// 2.创建cell
ChatCell *cell = nil;
if ([message.from isEqualToString:self.buddy.username]) { //好友
cell = [tableView dequeueReusableCellWithIdentifier:recivierCell];
}else{ //自己
cell = [tableView dequeueReusableCellWithIdentifier:senderCell];
}
// 3.给cell传递数据模型
cell.message = message;
// 4.返回
return cell;
}
#pragma mark - UITableViewDelegate
/**
* 返回cell的高度
*/
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 时间cell的高度是固定的
if ([self.dataSources[indexPath.row] isKindOfClass:[NSString class]]) {
return 20;
}
// 1.获取消息模型传递给cell,让cell有了数据然后调整布局
EMMessage *msg = self.dataSources[indexPath.row];
self.chatCellTool.message = msg;
// 2.必须上面cell有了数据调整完布局,才能得到高度
return [self.chatCellTool cellHeight];
}
#pragma mark - UITableViewDelegate
/**
* 开始滑动scrollView会调用
*/
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
// 停止语音播放
[AudioPlayTool stop];
}
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
// 1.计算textView的高度,调整整个inputToolbar的高度
CGFloat textViewH = 0;
CGFloat minH = 33; //textView最小高度
CGFloat maxH = 68; //textView最大高度
CGFloat contentH = textView.contentSize.height;
if (contentH < minH) {
textViewH = minH;
}else if (contentH > maxH){
textViewH = maxH;
}else{
textViewH = contentH;
}
// 2.监听用户是否点击了键盘的"Send"按钮 -- 判断最后一个字符是否是换行符
if ([textView.text hasSuffix:@"\n"]) {
// 发送消息
[self sendText:textView.text];
// 清空textView中的文字
textView.text = nil;
// 用户点击"发送"之后,textView高度还原
textViewH = minH;
}
// 3.调整整个inputToolbar的高度
/**
7为textView到顶部的距离
6为textView到底部的距离
*/
self.inputToolbarHeightConstraint.constant = 7 + 6 + textViewH;
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
}];
// 4.让光标回到原位
[textView setContentOffset:CGPointZero animated:YES];
[textView scrollRangeToVisible:textView.selectedRange];
}
#pragma mark - 发送消息(文本、语音、图片)
/**
* 发送消息
*
* @param body 消息体
*/
- (void)sendMessage:(id<IEMMessageBody>)body
{
// 1.构造消息对象
EMMessage *msg = [[EMMessage alloc] initWithReceiver:self.buddy.username bodies:@[body]];
msg.messageType = eMessageTypeChat;
// 2.发送消息
[[EaseMob sharedInstance].chatManager asyncSendMessage:msg progress:nil prepare:^(EMMessage *message, EMError *error) {
// 准备发送
} onQueue:nil completion:^(EMMessage *message, EMError *error) {
// 发送成功
} onQueue:nil];
// 4.把消息添加到数据源
[self addDataSourcesWithMessage:msg];
// 5.刷新表格
[self.tableView reloadData];
// 6.滚动tableView
[self scrollToBottom];
}
/**
* 发送文本消息
*/
- (void)sendText:(NSString *)text
{
// 0.处理字符串中的换行符
text = [text substringToIndex:text.length -1];
// 1.准备创建消息实例需要的参数
EMChatText *chatText = [[EMChatText alloc] initWithText:text];
EMTextMessageBody *textBody = [[EMTextMessageBody alloc] initWithChatObject:chatText];
// 2.发送
[self sendMessage:textBody];
}
/**
* 发送语音消息
*
* @param recordPath 语音文件路径
* @param duration 时间
*/
- (void)sendVoice:(NSString *)recordPath duration:(NSInteger)duration
{
// 1.构造一个 语音消息体
EMChatVoice *chatVoice = [[EMChatVoice alloc] initWithFile:recordPath displayName:@"[语音]"];
EMVoiceMessageBody *voiceBody = [[EMVoiceMessageBody alloc] initWithChatObject:chatVoice];
voiceBody.duration = duration;
// 2.发送
[self sendMessage:voiceBody];
}
/**
* 发送图片消息
*/
- (void)sendImage:(UIImage *)selectedImage
{
// 1.构造图片消息体
EMChatImage *orginalChatImg = [[EMChatImage alloc] initWithUIImage:selectedImage displayName:@"[图片]"];
EMImageMessageBody *imgBody = [[EMImageMessageBody alloc]initWithImage:orginalChatImg thumbnailImage:nil];
// 2.发送
[self sendMessage:imgBody];
}
/**
* tableView滚动到最后一行
*/
- (void)scrollToBottom
{
if (self.dataSources.count == 0) return;
NSIndexPath *lastIndex = [NSIndexPath indexPathForRow:self.dataSources.count-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:lastIndex atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
#pragma mark - EMChatManagerDelegate
/**
* 接收好友回复消息
*/
- (void)didReceiveMessage:(EMMessage *)message
{
if ([message.from isEqualToString:self.buddy.username]) {
// 把好友回复的消息添加到数据源
[self addDataSourcesWithMessage:message];
// 刷新表格
[self.tableView reloadData];
// 滚动tablView
[self scrollToBottom];
}
}
#pragma mark - action
/**
* 点击上传图片
*/
- (IBAction)showImgPickerAction:(UIButton *)sender
{
// 图片选择控制器
UIImagePickerController *imgPicker = [[UIImagePickerController alloc] init];
imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
imgPicker.delegate = self;
[self presentViewController:imgPicker animated:YES completion:nil];
}
#pragma mark - UIImagePickerControllerDelegate
/**
* 用户选中图片的回调
*/
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
// 获取选中的图片
UIImage *selectedImg = info[UIImagePickerControllerOriginalImage];
// 发送图片消息
[self sendImage:selectedImg];
// 退出当前图片选择的控制器
[self dismissViewControllerAnimated:YES completion:nil];
}
/**
* VoiceBtn被点击了
*/
- (IBAction)clickedVoiceBtn:(UIButton *)sender
{
// 按钮图片切换
self.voiceBtn.selected = !self.voiceBtn.selected;
self.textView.hidden = !self.textView.hidden;
// 1.显示录音按钮
self.recordBtn.hidden = !self.recordBtn.hidden;
if (self.recordBtn.hidden == NO) { //录音按钮要显示
// inputToolbar的高度要回到默认高度
self.inputToolbarHeightConstraint.constant = 46;
// 退出键盘
[self.view endEditing:YES];
}else{
// 当不录音的时候,键盘显示
[self.textView becomeFirstResponder];
// 恢复inputToolbar的高度
[self textViewDidChange:self.textView];
}
}
/**
* 手指按下去开始录音
*/
- (IBAction)touchDownRecordBtn:(id)sender
{
// 文件名以时间命令
int x = arc4random() % 100000;
NSTimeInterval time = [[NSDate date] timeIntervalSince1970];
NSString *fileName = [NSString stringWithFormat:@"%d%d",(int)time,x];
[[EMCDDeviceManager sharedInstance] asyncStartRecordingWithFileName:fileName completion:^(NSError *error) {
if (!error) {
//开始录音成功
}
}];
}
/**
* 手指松开结束录音
*/
- (IBAction)touchUpInsideRecordBtn:(id)sender
{
[[EMCDDeviceManager sharedInstance] asyncStopRecordingWithCompletion:^(NSString *recordPath, NSInteger aDuration, NSError *error) {
if (!error) {
//录音完成
//发送语音
[self sendVoice:recordPath duration:aDuration];
}
}];
}
/**
* 手指从按钮的外面松开结束录音
*/
- (IBAction)touchUpOutsideRecordBtn:(id)sender
{
}
/**
* 加入数据源的方法
*/
- (void)addDataSourcesWithMessage:(EMMessage *)message
{
// 1.判断EMMessage对象前面是否要加『时间』
NSString *timeStr = [TimeTool timeStr:message.timestamp];
if (![self.currentTimeStr isEqualToString:timeStr]) {
[self.dataSources addObject:timeStr];
self.currentTimeStr = timeStr;
}
// 2.再加EMMessage
[self.dataSources addObject:message];
// 3.设置消息为已读
[self.conversation markMessageWithId:message.messageId asRead:YES];
}
@end
自此,环信项目已完结!