聊天控制器(ChatViewController)界面搭建
14.聊天界面-工具条排版
1)搭建界面
添加聊天控制器:到mainstoryboard中找到addressbook的tableview控制器,将cell拖线给一个uiviewcontroller选择show,在该控制器导航栏中间拖一个navigationitem,修改名为“聊天界面”,拖一个uiew尺寸与底部tabar一样大小,并隐藏底部的tabar(1.点击控制器bottombar选择为none.2.点击控制器勾选Hide Bottom Bar
on Push)。(注意输入框为textview),中间部分拖一个tableview,自动布局,设置代理,注意tableview中有个属性,拖动时隐藏键盘(scrollview-keyboard-选择dissmiss on drag)
自定义该聊天控制器:chatviewcontroller,将storyboard中控制器class改为该控制器的类名。
2)实现键盘退出、弹出,工具栏紧链接功能:控制器代码如下:
//1.将工具条底部的约束拖线为该控制器的属性。
@property(weak,nonatomic)IBOutletNSLayoutConstraint*chatInputBottomConstant;
//2.viewdidload中监听键盘弹出/回方法
//1).监听键盘弹出,把inputToolbar(输入工具条)往上移
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(kbWillShow:)name:UIKeyboardWillShowNotificationobject:nil];
//2).监听键盘退出,inputToolbar恢复原位
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(kbWillHide:)name:UIKeyboardWillHideNotificationobject:nil];
//3.实现监听方法
#pragma mark键盘显示时会触发的方法
-(void)kbWillShow:(NSNotification*)noti{
//1.获取键盘高度
//1.1获取键盘结束时候的位置
CGRectkbEndFrm = [noti.userInfo[UIKeyboardFrameEndUserInfoKey]CGRectValue];
CGFloatkbHeight = kbEndFrm.size.height;
//2.更改inputToolbar底部约束
self.chatInputBottomConstant.constant= kbHeight;
//添加动画
[UIViewanimateWithDuration:0.25animations:^{
[self.viewlayoutIfNeeded];
}];
}
#pragma mark键盘退出时会触发的方法
-(void)kbWillHide:(NSNotification*)noti{
//inputToolbar恢复原位
self.chatInputBottomConstant.constant=0;
}
//4.注销监听者
-(void)dealloc{
[[NSNotificationCenterdefaultCenter]removeObserver:self];
}
15.聊天界面-接收方cell的排版
经过分析,cell有三种类型:接受方cell、发送方cell、时间cell。
在刚才拖入的tableview上拖一个cell重命名为receivecell,再改cell中拖入控件并自动布局,给cell设置重用标识为receivecell
注意:布局消息框:思想:底部为图片,上面为label,先布局label,然后布局imageview,让他与label大小一样,然后修改约束使iamgeview周围都比label周围大一点。
A.布局label:Label布局为距离顶部15,左边20。换行:点击label-lines = 0、(设置label最大宽度)点击尺子- preferred width = 242勾选,回车
B.布局uiiamgeview:cell中拖一个UIimageview,从左边的列表中找到该imageview,拖到label的后面,同时选中label、iamgeview、设置四边距都对齐约束,然后update,给iamgeview添加背景图片,图片拉伸:点击imageview-streching-x:0.5/y:0.7,其余都为0。找到iamgeview的所有约束,给四周约束分别调10个间距大小。
自定义cell,view-ChatCell,将刚才storyboard中的cell的class改为该类。将上面那个label拖线到.h文件(必须)为属性
控制器中数据源和代理的方法实现如下(测试而已):
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{
return20;
}
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
return200;
}
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
staticNSString*ID =@"ReceiverCell";
ChatCell*cell = [tableViewdequeueReusableCellWithIdentifier:ID];
//显示内容
cell.chatLabel.text=@"asrqwerqwerqwerqwerqwfaskdjlfalsk;dfjalksjdflaksjdfa;sjdfklajsdkfljasfkal;sdkfjalksjdfa;jsdflkajsdf;lajskdf;las";
returncell;
}
16.聊天界面-发送方cell的排版
1).tableview中再拖一个cell,同理搭建sendcell(注意:将label脱线到cell中的同一个属性中)
2).设置cell的高度:在自定义cell中实现计算cell的高度)
//专门用来计算高度的一个cell(注意理解)
#import
staticNSString*ReceiverCell =@"ReceiverCell";
staticNSString*SenderCell =@"SenderCell";
@interfaceChatCell :UITableViewCell
@property(weak,nonatomic)IBOutletUILabel*chatLabel;
-(CGFloat)cellHeghit;
@end
#import"ChatCell.h"
@implementationChatCell
/**返回cell的高度*/
-(CGFloat)cellHeghit{
//1.重新布局子控件
[selflayoutIfNeeded];
return5+10+self.chatLabel.bounds.size.height+10+5;
}
@end
3).控制器中实现的方法
a.添加属性
@property(weak,nonatomic)IBOutletUITableView*tableView;
/**数据源*/
@property(nonatomic,strong)NSMutableArray*dataSources;
/**计算高度的cell工具对象(就是一个工具,给我文字长度我就能计算高度)*/
@property(nonatomic,strong)ChatCell*chatCellTool;
b.viewdidload初始化数据源
-(NSMutableArray*)dataSources{
if(!_dataSources) {
_dataSources= [NSMutableArrayarray];
}
return_dataSources;
}
//初始化数(好几个长的)据
[self.dataSourcesaddObject:@"xcsafasdffsadfa"];
[self.dataSourcesaddObject:@"xcsafasdfsadxcssafasdfsadfafa"];
[self.dataSourcesaddObject:@"xcsafasdfxcsaadfasadfa"];
//给计算高度的cell工具对象赋值(就是初始化工具对象,也可以为sendcell)
self.chatCellTool= [self.tableViewdequeueReusableCellWithIdentifier:ReceiverCell];
c.重新实现数据源方法
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{
returnself.dataSources.count;
}
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
//设置label的数据
#warning计算高度与前,一定要给chatLabel.text赋值
self.chatCellTool.chatLabel.text=self.dataSources[indexPath.row];
return[self.chatCellToolcellHeghit];
}
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
ChatCell*cell =nil;
if(indexPath.row%2==0) {//发送方的cell
cell = [tableViewdequeueReusableCellWithIdentifier:SenderCell];
}else{//接收发方的cell
cell = [tableViewdequeueReusableCellWithIdentifier:ReceiverCell];
}
//显示内容
cell.chatLabel.text=self.dataSources[indexPath.row];
returncell;
}
消息管理
消息:IM交互实体,在SDK中对应的类型是EMMessage。EMMessage由EMMessageBody组成.
总结:与消息相关的网络请求都用到[[EMClientsharedClient].chatManager聊天管理者
获取一个人的会话列表用EMConversation类
17.聊天界面-发送聊天消息
步骤:
在mainstoryboard中找到聊天界面的输入框textView,点击-return-send
监听该send事件:拖线该textfield的代理到聊天控制器(ChatViewController)。控制器遵守代理(UITextViewDelegate),并实现代理方法
导入头文件:#import "EMSDK.h"(用多了可以添加到pch文件中)
1).给该控制器添加一个外部属性
//要发送人的名字
@property(nonatomic,copy)NSString*buddy;
2).在通讯录控制器(AddressBookViewController)中传值,代码如下:
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender{
//往聊天控制器传递一个buddy的值
iddestVC = segue.destinationViewController;
if([destVCisKindOfClass:[ChatViewControllerclass]]) {
//获取点击的行
NSIntegerselectedRow = [self.tableViewindexPathForSelectedRow].row;
ChatViewController*chatVc = destVC;
chatVc.buddy=self.boddyList[selectedRow];
}
}
3).聊天控制器(ChatViewController)中发送消息代码如下
#pragma mark -UITextViewDelegate
-(void)textViewDidChange:(UITextView*)textView{
//监听Send事件--判断最后的一个字符是不是换行字符(因为点击send时,textfield会换行)
if([textView.texthasSuffix:@"\n"]) {
NSLog(@"发送操作");
[selfsendMessage:textView.text];
//清空textView的文字
textView.text=nil;
}
}
//发送消息
-(void)sendMessage:(NSString*)text{
//把最后一个换行字符去除
#warning换行字符只占用一个长度
text = [textsubstringToIndex:text.length-1];
//消息=消息头+消息体
#warning每一种消息类型对象不同的消息体
//EMTextMessageBody:文字
//EMImageMessageBody:图片
//EMLocationMessageBody:位置
//EMVoiceMessageBody:语音
//EMVideoMessageBody:视频
//EMFileMessageBody:文件
//EMCmdMessageBody:透传
//1.创建文字消息体
EMTextMessageBody*body = [[EMTextMessageBodyalloc]initWithText:text];
NSString*from = [[EMClientsharedClient]currentUsername];
//2.生成Message消息对象
EMMessage*message = [[EMMessagealloc]initWithConversationID:self.buddyfrom:fromto:self.buddybody:bodyext:nil];
message.chatType=EMChatTypeChat;//设置为单聊消息
//消息类型为:
//EMChatTypeChat:设置为单聊消息
// EMChatTypeGroupChat:设置为群聊消息
//EMChatTypeChatRoom:设置为聊天室消息
//3.发送消息
//发送所有类型的消息都用这个接口,只是消息类型不同
[[EMClientsharedClient].chatManagersendMessage:messageprogress:^(intprogress) {
}completion:^(EMMessage*message,EMError*error) {
NSLog(@"完成消息发送%@",error);
}];
// 4.把消息添加到数据源,然后再刷新表格(发送完消息立即显示)
[self.dataSourcesaddObject:message];
[self.tableViewreloadData];
// 5.把消息显示在顶部(发送完消息自动滚动到键盘顶部)
[selfscrollToBottom];
}
-(void)scrollToBottom{
//1.获取最后一行
if(self.dataSources.count==0) {
return;
}
NSIndexPath*lastIndex = [NSIndexPathindexPathForRow:self.dataSources.count-1inSection:0];
[self.tableViewscrollToRowAtIndexPath:lastIndexatScrollPosition:UITableViewScrollPositionBottomanimated:YES];
}
18.聊天界面-添加本地聊天记录
1).在聊天控制器中的viewdidload方法中添加当前navigation的title为好友的名字,加载本地聊天记录
//显示好友的名字
self.title=self.buddy;
//加载本地数据库聊天记录(MessageV1)
[selfloadLocalChatRecords];
-(void)loadLocalChatRecords{
//要获取本地聊天记录使用会话对象
//获取一个会话
//EMConversationTypeChat单聊会话
//EMConversationTypeGroupChat群聊会话
//EMConversationTypeChatRoom聊天室会话
EMConversation*conversation = [[EMClientsharedClient].chatManagergetConversation:self.buddytype:EMConversationTypeChatcreateIfNotExist:YES];
//加载与当前聊天用户所有聊天记录
// fromUser:若传好友名字,则只会加载好友发的聊天记录,若为nil加载两方的
longlongtimestamp = [[NSDatedate]timeIntervalSince1970] *1000+1;
[conversationloadMessagesWithType:EMMessageBodyTypeTexttimestamp:timestampcount:100fromUser:nilsearchDirection:EMMessageSearchDirectionUpcompletion:^(NSArray*aMessages,EMError*aError) {
//aMessages内部为EMMessage对象
//添加到数据源
[self.dataSourcesaddObjectsFromArray:aMessages];
[self.tableViewreloadData];
}];
}
2).在自定义cellChatCell中添加外部模型属性,并实现set方法,封装
/**消息模型,内部set方法显示文字*/
@property(nonatomic,strong)EMMessage*message;
-(void)setMessage:(EMMessage*)message{
_message= message;
// 1.获取消息体
idbody = message.body;
if([bodyisKindOfClass:[EMTextMessageBodyclass]]) {//文本消息
EMTextMessageBody*textBody = body;
self.chatLabel.text= textBody.text;
}elseif([bodyisKindOfClass:[EMVoiceMessageBodyclass]]){//语音消息
self.chatLabel.text=@"【语音】";
}
else{
self.chatLabel.text=@"未知类型";
}
}
3).在聊天控制器中修改tableview的数据源方法
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{
returnself.dataSources.count;
}
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
//设置label的数据
NSLog(@"%@",self.dataSources[indexPath.row]);
// 1.获取消息模型
EMMessage*msg =self.dataSources[indexPath.row];
self.chatCellTool.message= msg;
return[self.chatCellToolcellHeghit];
}
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
//1.先获取消息模型
EMMessage*message =self.dataSources[indexPath.row];
ChatCell*cell =nil;
if([message.fromisEqualToString:self.buddy]) {//接收方
cell = [tableViewdequeueReusableCellWithIdentifier:ReceiverCell];
}else{//发送方
cell = [tableViewdequeueReusableCellWithIdentifier:SenderCell];
}
//显示内容
cell.message= message;
returncell;
}
29.聊天界面-监听消息回复(将对方发的消息及时显示在当前聊天界面)
聊天控制器中的viewdidload中设置聊天管理器代理并遵守协议EMChatManagerDelegate
[[EMClientsharedClient].chatManageraddDelegate:selfdelegateQueue:nil];
//实现代理方法
#pragma mark -EMChatManagerDelegate
//收到一条以上消息
- (void)messagesDidReceive:(NSArray*)aMessages{
for(EMMessage*messageinaMessages) {
#warning from一定等于当前聊天用户才可以刷新数据(当test1 - test7两者聊天时,test8发过来的消息不能显示在该聊天记录界面)
if([message.fromisEqualToString:self.buddy]) {
//1.把接收的消息添加到数据源
[self.dataSourcesaddObject:message];
//2.刷新表格
[self.tableViewreloadData];
//3.显示数据到底部
[selfscrollToBottom];
}
}
}
20.完善聊天输入框
实现输入框自动改变高度:
1).找到mainstoryboard中的聊天控制器界面的ChatInputToolBar找到他的高度属性,并拖线到聊天控制器成为其属性
@property(weak,nonatomic)IBOutletNSLayoutConstraint*inputbarHeightConstant;
2).在聊天控制器ChatViewController中的监听文字改变的方法中添加:
#pragma mark -UITextViewDelegate
-(void)textViewDidChange:(UITextView*)textView{
//监视textView.contentOffset的变化
NSLog(@"contentOffset %@",NSStringFromCGPoint(textView.contentOffset));
// 1.计算TextView的高度,调整整个intputbar的高度
CGFloattextViewH =0;
CGFloatminHeight =33;//textView最小的高度
CGFloatmaxHeight =68;//textView最大的高度
//获取contentSize的高度(因为textView继承自scrollview)
CGFloatcontentHeight = textView.contentSize.height;
if(contentHeight < minHeight) {
textViewH = minHeight;
}elseif(contentHeight > maxHeight){
textViewH = maxHeight;
}else{
textViewH = contentHeight;
}
// 2.监听Send事件--判断最后的一个字符是不是换行字符(因为点击send时,textfield会换行)
if([textView.texthasSuffix:@"\n"]) {
NSLog(@"发送操作");
[selfsendMessage:textView.text];
//清空textView的文字
textView.text=nil;
//发送时,textViewH的高度为33
textViewH = minHeight;
}
// 3.调整整个InputToolBar高度
self.inputbarHeightConstant.constant=6+7+ textViewH;
//加个动画
[UIViewanimateWithDuration:0.25animations:^{
[self.viewlayoutIfNeeded];
}];
// 4.记光标回到原位
#warning技巧
[textViewsetContentOffset:CGPointZeroanimated:YES];
[textViewscrollRangeToVisible:textView.selectedRange];
}
3).给textview添加背景:
在textview后面加一个背景图片(imageview),在mainstoryboard中找到ChatInputToolBar,往里面拖一个imageview,设置其约束与textview一样,添加背景图片(并设置拉伸效果),将textView的背景色改为透明
21.发送语音
1).点击左边的话筒按钮,输入框变为发语音框,自动布局
在mainstoryboard中的聊天控制器界面的ChatInputToolBar中拖一个button,直接盖住textView上面,修改文字为“按住说话”,修改高亮时的文字为“松开发送”,设置约束,固定高度,选择默认为隐藏,拖线监听左边的话筒按钮:
//语音框
@property(weak,nonatomic)IBOutletUIButton*recordBtn;
//监听语音点击按钮
- (IBAction)voiceAction:(id)sender {
//1.显示录音按钮
self.recordBtn.hidden= !self.recordBtn.hidden;
self.textView.hidden= !self.textView.hidden;
}
2).到环信官方demo中EaseUI-中找到DeviceHelper导入框架,EaseLocalDefine.h/EaseUIResource.bundle也导入框架
推进项目的Lib文件。再改控制器中导入头文件#import"EMCDDeviceManager.h"
拖线监听语音框,监听按下去时,动作(选中该按钮,),找到点下触发,拖线监听:
#pragma mark按钮点下去开始录音
//开始录音
- (IBAction)beginVoiceAction:(id)sender {
//文件名以时间命名(根据时间定义文件的名字)
//filename:录音将要存放的文件(自动存放沙盒)
intx =arc4random() %100000;
NSTimeIntervaltime = [[NSDatedate]timeIntervalSince1970];
NSString*fileName = [NSStringstringWithFormat:@"%d%d",(int)time,x];
NSLog(@"按钮点下去开始录音");
//这个方法,就是那个框架(DeviceHelper)中的方法
[[EMCDDeviceManagersharedInstance]asyncStartRecordingWithFileName:fileNamecompletion:^(NSError*error) {
if(!error) {
NSLog(@"开始录音成功");
}
}];
}
#pragma mark手指从按钮范围内松开结束录音
//结束录音
- (IBAction)endVoiceAction:(id)sender {
[[EMCDDeviceManagersharedInstance]asyncStopRecordingWithCompletion:^(NSString*recordPath,NSIntegeraDuration,NSError*error) {
//recordPath:录音的路径
//aDuration:录音的时长
if(!error) {
NSLog(@"录音成功");
NSLog(@"%@",recordPath);
//发送语音给服务器
[selfsendVoice:recordPathduration:aDuration];
}else{
NSLog(@"== %@",error);
}
}];
}
#pragma mark手指从按钮外面松开取消录音
//取消录音
- (IBAction)cancelVoiceAction:(id)sender {
[[EMCDDeviceManagersharedInstance]cancelCurrentRecording];
}
#pragma mark发送语音消息
//发送语音
-(void)sendVoice:(NSString*)recordPath duration:(NSInteger)duration{
//1.创建语音消息体
EMVoiceMessageBody*body = [[EMVoiceMessageBodyalloc]initWithLocalPath:recordPathdisplayName:@"语音"];
body.duration= (int)duration;
NSString*from = [[EMClientsharedClient]currentUsername];
//2.生成voice消息对象
EMMessage*voice = [[EMMessagealloc]initWithConversationID:self.buddyfrom:fromto:self.buddybody:bodyext:nil];
voice.chatType=EMChatTypeChat;//设置为单聊消息
//3.发送消息
//发送所有类型的消息都用这个接口,只是消息类型不同
[[EMClientsharedClient].chatManagersendMessage:voiceprogress:^(intprogress) {
}completion:^(EMMessage*message,EMError*error) {
NSLog(@"完成消息发送%@",error);
}];
// 4.把消息添加到数据源,然后再刷新表格(发送完消息立即显示)
[self.dataSourcesaddObject:voice];
[self.tableViewreloadData];
// 5.把消息显示在顶部(发送完消息自动滚动到键盘顶部)
[selfscrollToBottom];
}
22.播放语音
1).//显示语音的形式为图片加文字,在ChatCell中实现方法:
#pragma mark返回语音富文本
-(NSAttributedString*)voiceAtt{
//创建一个可变的富文本
NSMutableAttributedString*voiceAttM = [[NSMutableAttributedStringalloc]init];
// 1.接收方:富文本=图片+时间
if([self.reuseIdentifierisEqualToString:ReceiverCell]) {
// 1.1接收方的语音图片
UIImage*receiverImg = [UIImageimageNamed:@"chat_receiver_audio_playing_full"];
// 1.2创建图片附件
NSTextAttachment*imgAttachment = [[NSTextAttachmentalloc]init];
imgAttachment.image= receiverImg;
imgAttachment.bounds=CGRectMake(0, -7,30,30);
// 1.3图片富文本
NSAttributedString*imgAtt = [NSAttributedStringattributedStringWithAttachment:imgAttachment];
[voiceAttMappendAttributedString:imgAtt];
// 1.4.创建时间富文本
//获取时间
EMVoiceMessageBody*voiceBody = (EMVoiceMessageBody*)self.message.body;
NSIntegerduration = voiceBody.duration;
NSString*timeStr = [NSStringstringWithFormat:@"%ld'",duration];
NSAttributedString*timeAtt = [[NSAttributedStringalloc]initWithString:timeStr];
[voiceAttMappendAttributedString:timeAtt];
}else{
// 2.发送方:富文本=时间+图片
// 2.1拼接时间
//获取时间
EMVoiceMessageBody*voiceBody = (EMVoiceMessageBody*)self.message.body;
NSIntegerduration = voiceBody.duration;
NSString*timeStr = [NSStringstringWithFormat:@"%ld'",duration];
NSAttributedString*timeAtt = [[NSAttributedStringalloc]initWithString:timeStr];
[voiceAttMappendAttributedString:timeAtt];
// 2.1拼接图片
UIImage*receiverImg = [UIImageimageNamed:@"chat_sender_audio_playing_full"];
//创建图片附件
NSTextAttachment*imgAttachment = [[NSTextAttachmentalloc]init];
imgAttachment.image= receiverImg;
imgAttachment.bounds=CGRectMake(0, -7,30,30);
//图片富文本
NSAttributedString*imgAtt = [NSAttributedStringattributedStringWithAttachment:imgAttachment];
[voiceAttMappendAttributedString:imgAtt];
}
return[voiceAttMcopy];
}
2).在该方法中该setMessage:(EMMessage*)message:
self.chatLabel.text = @"【语音】";
self.chatLabel.attributedText= [selfvoiceAtt];
3).给语音消息添加点击手势,并监听方法:(在mainstoryboard中找到,消息label打开用户交互)
//导入头文件:#import"EMCDDeviceManager.h"#import"AudioPlayTool.h"
-(void)awakeFromNib{
//在此方法做一些初始化操作
// 1.给label添加敲击手势
UITapGestureRecognizer*tap = [[UITapGestureRecognizeralloc]initWithTarget:selfaction:@selector(messageLabelTap:)];
[self.chatLabeladdGestureRecognizer:tap];
}
#pragma mark messagelabel点击的触发方法
-(void)messageLabelTap:(UITapGestureRecognizer*)recognizer{
NSLog(@"%s",__func__);
//播放语音
//只有当前的类型是为语音的时候才播放
//1.获取消息体
idbody =self.message.body;
if([bodyisKindOfClass:[EMVoiceMessageBodyclass]]) {
NSLog(@"播放语音");
BOOLreceiver = [self.reuseIdentifierisEqualToString:ReceiverCell];
[AudioPlayToolplayWithMessage:self.messagemsgLabel:self.chatLabelreceiver:receiver];
}
}
4).写一个工具类来播放语音goup –chart-Tool-AudioPlayTool
#import
#import"EMSDK.h"
@interfaceAudioPlayTool :NSObject
+(void)playWithMessage:(EMMessage*)msg msgLabel:(UILabel*)msgLabel receiver:(BOOL)receiver;
@end
#import"AudioPlayTool.h"
#import"EMCDDeviceManager.h"
//用imageview播放动画(好好研究)
staticUIImageView*animatingImageView;//正在执行动画的ImageView
@implementationAudioPlayTool
+(void)playWithMessage:(EMMessage*)msg msgLabel:(UILabel*)msgLabel receiver:(BOOL)receiver{
//把以前的动画移除
[animatingImageViewstopAnimating];
[animatingImageViewremoveFromSuperview];
//1.播放语音
//获取语音路径
EMVoiceMessageBody*voiceBody = (EMVoiceMessageBody*) msg.body;
// localPath:本地音频路径
// remotePath:服务器音频路径
//本地语音文件路径
NSString*path = voiceBody.localPath;
//如果本地语音文件不存在,使用服务器语音
NSFileManager*manager = [NSFileManagerdefaultManager];
if(![managerfileExistsAtPath:path]) {
path = voiceBody.remotePath;
}
//播放语音
[[EMCDDeviceManagersharedInstance]asyncPlayingWithPath:pathcompletion:^(NSError*error) {
NSLog(@"语音播放完成%@",error);
//移除动画
[animatingImageViewstopAnimating];
[animatingImageViewremoveFromSuperview];
}];
//2.添加动画(点击语音消息时有动画)让UIImageView播放动画、动画的内部实现为图片的轮播
//2.1创建一个UIImageView添加到Label上
UIImageView*imgView = [[UIImageViewalloc]init];
[msgLabeladdSubview:imgView];
//2.2添加动画图片
if(receiver) {
imgView.animationImages=@[[UIImageimageNamed:@"chat_receiver_audio_playing000"],
[UIImageimageNamed:@"chat_receiver_audio_playing001"],
[UIImageimageNamed:@"chat_receiver_audio_playing002"],
[UIImageimageNamed:@"chat_receiver_audio_playing003"]];
imgView.frame=CGRectMake(0,0,30,30);
}else{
imgView.animationImages=@[[UIImageimageNamed:@"chat_sender_audio_playing_000"],
[UIImageimageNamed:@"chat_sender_audio_playing_001"],
[UIImageimageNamed:@"chat_sender_audio_playing_002"],
[UIImageimageNamed:@"chat_sender_audio_playing_003"]];
imgView.frame=CGRectMake(msgLabel.bounds.size.width-30,0,30,30);
}
imgView.animationDuration=1;
[imgViewstartAnimating];
animatingImageView= imgView;
}
@end
5).//解决bug:当输入多行文字时,再点语音按钮,此时的textView栏不恢复原来高度,则:
在聊天控制器XMGChatViewController中的
//监听语音点击按钮
- (IBAction)voiceAction:(id)sender {
// 1.显示录音按钮
self.recordBtn.hidden= !self.recordBtn.hidden;
if(self.recordBtn.hidden==NO) {//录音按钮要显示
//InputToolBar的高度要回来默认(46);
self.inputbarHeightConstant.constant=46;
//隐藏键盘
[self.viewendEditing:YES];
}else{
//当不录音的时候,键盘显示
[self.textViewbecomeFirstResponder];
//恢复InputToolBar高度(输入几行文字,点击语音按钮,再点会文字按钮,textView高度不变)
[selftextViewDidChange:self.textView];
}
}
23.发送图片显示图片
A.发送图片
1).点击输入框的右边的+按钮,到相册选择图片
拖线到聊天控制器中监听方法:
- (IBAction)showImgPickerAction:(id)sender {
//显示图片选择的控制器
UIImagePickerController*imgPicker = [[UIImagePickerControlleralloc]init];
//设置源
imgPicker.sourceType=UIImagePickerControllerSourceTypePhotoLibrary;
imgPicker.delegate=self;
[selfpresentViewController:imgPickeranimated:YEScompletion:NULL];
/**用户选中图片的回调(代理方法)*/
-(void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
//1.获取用户选中的图片
UIImage*selectedImg =info[UIImagePickerControllerOriginalImage];
//2.发送图片
[selfsendImg:selectedImg];
//3.隐藏当前图片选择控制器
[selfdismissViewControllerAnimated:YEScompletion:NULL];
}
}
#pragma mark发送图片
-(void)sendImg:(UIImage*)selectedImg{
//1.构造图片消息体
/*
*第一个参数:原始大小的图片对象1000 * 1000
*第二个参数:缩略图的图片对象120 * 120
*/
//将image转化为data
NSData*imageData =UIImagePNGRepresentation(selectedImg);
EMImageMessageBody*body = [[EMImageMessageBodyalloc]initWithData:imageDatathumbnailData:nil];
NSString*from = [[EMClientsharedClient]currentUsername];
//2.生成image消息对象
EMMessage*imageMessage = [[EMMessagealloc]initWithConversationID:self.buddyfrom:fromto:self.buddybody:bodyext:nil];
imageMessage.chatType=EMChatTypeChat;//设置为单聊消息
//3.发送消息
//发送所有类型的消息都用这个接口,只是消息类型不同
[[EMClientsharedClient].chatManagersendMessage:imageMessageprogress:^(intprogress) {
}completion:^(EMMessage*message,EMError*error) {
NSLog(@"完成消息发送%@",error);
}];
// 4.把消息添加到数据源,然后再刷新表格(发送完消息立即显示)
[self.dataSourcesaddObject:imageMessage];
[self.tableViewreloadData];
// 5.把消息显示在顶部(发送完消息自动滚动到键盘顶部)
[selfscrollToBottom];
}
2).抽取方法(把发送文本的方法名改为sendText,由于发文本、图片、语音都有这一个段代码,因此抽成一个方法)
-(void)sendMessage:(EMMessageBody*)body{
//1.生成Message消息对象
NSString*from = [[EMClientsharedClient]currentUsername];
EMMessage*message = [[EMMessagealloc]initWithConversationID:self.buddyfrom:fromto:self.buddybody:bodyext:nil];
message.chatType=EMChatTypeChat;//设置为单聊消息
//消息类型为:
//EMChatTypeChat:设置为单聊消息
// EMChatTypeGroupChat:设置为群聊消息
//EMChatTypeChatRoom:设置为聊天室消息
//2.发送消息
//发送所有类型的消息都用这个接口,只是消息类型不同
[[EMClientsharedClient].chatManagersendMessage:messageprogress:^(intprogress) {
}completion:^(EMMessage*message,EMError*error) {
NSLog(@"完成消息发送%@",error);
}];
// 3.把消息添加到数据源,然后再刷新表格(发送完消息立即显示)
[self.dataSourcesaddObject:message];
[self.tableViewreloadData];
// 4.把消息显示在顶部(发送完消息自动滚动到键盘顶部)
[selfscrollToBottom];
}
B.显示图片
拖入SDWebImage框架,导入#import"UIImageView+WebCache.h"
在ChatCell。M文件中-(void)setMessage:(EMMessage*)message的方法中
添加elseif([bodyisKindOfClass:[EMImageMessageBodyclass]]){//图片消息
[selfshowImage]; }
-(void)showImage{
//获取图片消息体
EMImageMessageBody*imgBody = (EMImageMessageBody*)self.message.body;
CGRectthumbnailFrm = (CGRect){0,0,imgBody.thumbnailSize};
//设置Label的尺寸足够显示UIImageView
NSTextAttachment*imgAttach = [[NSTextAttachmentalloc]init];
imgAttach.bounds= thumbnailFrm;
NSAttributedString*imgAtt = [NSAttributedStringattributedStringWithAttachment:imgAttach];
self.chatLabel.attributedText= imgAtt;
//1.cell里添加一个UIImageView
[self.messageLabel addSubview:self.chatImgView];
//2.设置图片控件为缩略图的尺寸
self.chatImgView.frame= thumbnailFrm;
//3.下载图片
NSLog(@"thumbnailLocalPath %@",imgBody.thumbnailLocalPath);
NSLog(@"thumbnailRemotePath %@",imgBody.thumbnailRemotePath);
NSFileManager*manager = [NSFileManagerdefaultManager];
//如果本地图片存在,直接从本地显示图片
UIImage*palceImg = [UIImageimageNamed:@"downloading"];
if([managerfileExistsAtPath:imgBody.thumbnailLocalPath]) {
#warning本地路径使用fileURLWithPath方法,网络路径使用URLWithString:
[self.chatImgViewsd_setImageWithURL:[NSURLfileURLWithPath:imgBody.thumbnailLocalPath]placeholderImage:palceImg];
}else{
//如果本地图片不存,从网络加载图片
[self.chatImgViewsd_setImageWithURL:[NSURLURLWithString:imgBody.thumbnailRemotePath]placeholderImage:palceImg];
}
}
C.修改bug
1).图片cell会重用,重用的时候应该讲图片移除;在XMGChatCell中添加
添加属性
/**聊天图片控件*/
@property(nonatomic,strong)UIImageView*chatImgView;
-(UIImageView*)chatImgView{
if(!_chatImgView) {
_chatImgView= [[UIImageViewalloc]init];
}
return_chatImgView;
}
-(void)setMessage:(EMMessage *)message{
//重用时,把聊天图片控件移除
[self.chatImgView removeFromSuperview];
滑动聊天控制器时不能在播放语音:
在AudioPlayTool中添加方法
+(void)stop;
+(void)stop{
//停止播放语音
[[EMCDDeviceManagersharedInstance]stopPlaying];
//移除动画
[animatingImageViewstopAnimating];
[animatingImageViewremoveFromSuperview];
}
在聊天控制器中调用:
-(void)scrollViewWillBeginDragging:(UIScrollView*)scrollView{
//停止语音播放
[AudioPlayToolstop];
}
24.显示时间的cell
时间显示的规则
同一分中内的消息,只显示一个时间
/*
15:52
msg1 15:52:10
msg2 15:52:08
msg2 15:52:02
*/
/*今天:时:分(HH:mm)
*昨天:昨天+时+分(昨天HH:mm)
*昨天以前:(前天)年:月:日时分(2015-09-26 15:27)
*/
在mainstoryboard中的聊天控制器中拖一个时间cell,拖一个label自动布局,自定义cell(TimeCell)为该类,并将label拖线为属性。设置重用标识。
分析:数据源数组中不仅有模型对象,也有时间字符串对象,因此,在数据源实现cell方法中,导入TimeCell头文件添加
//判断数据源类型
if([self.dataSources[indexPath.row]isKindOfClass:[NSStringclass]]) {//显示时间cell
XMGTimeCell*timeCell = [tableViewdequeueReusableCellWithIdentifier:@"TimeCell"];
timeCell.timeLabel.text=self.dataSources[indexPath.row];
returntimeCell;
}
在cell高度数据源方法中设置时间cell的高度
//时间cell的高度是固定
if([self.dataSources[indexPath.row]isKindOfClass:[NSStringclass]]) {
return18;
}
25.显示时间的计算
新建一个时间计算工具类,继承自nsobject(思想牛逼)
#import
@interfaceTimeTool : NSObject
//时间戳
+(NSString *)timeStr:(longlong)timestamp;
@end
#import"TimeTool.h"
@implementationTimeTool
+(NSString *)timeStr:(longlong)timestamp{
//返回时间格式
//currentDate 2015-09-28 16:28:09 +0000
//msgDate 2015-09-28 10:36:22 +0000
NSCalendar*calendar =[NSCalendar currentCalendar];
//1.获取当前的时间
NSDate *currentDate = [NSDate date];
//获取年,月,日
NSDateComponents *components = [calendarcomponents:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:currentDate];
NSInteger currentYear = components.year;
NSInteger currentMonth = components.month;
NSInteger currentDay = components.day;
NSLog(@"currentYear
%ld",components.year);
NSLog(@"currentMonth
%ld",components.month);
NSLog(@"currentDay
%ld",components.day);
//2.获取消息发送时间
NSDate *msgDate = [NSDate dateWithTimeIntervalSince1970:timestamp/1000.0];
//获取年,月,日
components = [calendarcomponents:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDayfromDate:msgDate];
CGFloat msgYead = components.year;
CGFloat msgMonth = components.month;
CGFloat msgDay = components.day;
NSLog(@"msgYear
%ld",components.year);
NSLog(@"msgMonth
%ld",components.month);
NSLog(@"msgDay
%ld",components.day);
//3.判断:
/*今天:(HH:mm)
*昨天: (昨天HH:mm)
*昨天以前:(2015-09-26 15:27)
*/
NSDateFormatter *dateFmt = [[NSDateFormatter alloc] init];
if(currentYear == msgYead
&& currentMonth == msgMonth
&& currentDay == msgDay) {//今天
dateFmt.dateFormat=@"HH:mm";
}elseif(currentYear == msgYead
&& currentMonth == msgMonth
&& currentDay -1== msgDay){//昨天
dateFmt.dateFormat=@"昨天HH:mm";
}else{//昨天以前
dateFmt.dateFormat=@"yyy-MM-dd
HH:mm";
}
return[dateFmt stringFromDate:msgDate];
}
@end
由于datasource数组需要判断什么时间添加模型,什么时间添加时间字符串因此写一个独立的方法:在聊天控制器中修改
/**当前添加的时间*/
@property(nonatomic,copy)NSString*currentTimeStr;
-(void)addDataSourcesWithMessage:(EMMessage*)msg{
// 1.判断EMMessage对象前面是否要加"时间"
//if (self.dataSources.count == 0) {
////long long timestamp = ([[NSDate date] timeIntervalSince1970] - 60 * 60 *24 * 2) * 1000;
//
//}
NSString*timeStr = [XMGTimeTooltimeStr:msg.timestamp];
if(![self.currentTimeStrisEqualToString:timeStr]) {
[self.dataSourcesaddObject:timeStr];
self.currentTimeStr= timeStr;
}
// 2.再加EMMessage
[self.dataSourcesaddObject:msg];
}
修改加载数据方法:
-(void)loadLocalChatRecords{
//假设在数组的第一位置添加时间
//[self.dataSources addObject:@"16:06"];
//要获取本地聊天记录使用会话对象
EMConversation*conversation = [[EaseMobsharedInstance].chatManagerconversationForChatter:self.buddy.usernameconversationType:eConversationTypeChat];
//加载与当前聊天用户所有聊天记录
NSArray*messages = [conversationloadAllMessages];
//for (id obj in messages) {
//NSLog(@"%@",[obj class]);
//}
//添加到数据源
//[self.dataSources addObjectsFromArray:messages];
for(EMMessage*msgObjinmessages) {
[selfaddDataSourcesWithMessage:msgObj];
}
}
修改聊天控制器中添加数据源数组方法:
-(void)sendMessage:(id)body{
[selfaddDataSourcesWithMessage:msgObj];
-(void)didReceiveMessage:(EMMessage*)message{
//1.把接收的消息添加到数据源
//[self.dataSources addObject:message];
[selfaddDataSourcesWithMessage:message];
27.显示历史会话记录(就是显示给谁聊过天)
在会话控制器XMGConversationViewController中
/**历史会话记录*/
@property(nonatomic,strong)NSArray*conversations;
viewDidLoad添加:
//添加聊天管理者代理
[[EMClientsharedClient].chatManageraddDelegate:selfdelegateQueue:nil];
//获取历史会话记录
[selfloadConversations];
-(void)loadConversations{
//获取历史会话记录
//1.从内存中获取历史回话记录,获取内存中所有会话
//获取所有会话,如果内存中不存在会从DB中加载
NSArray*conversations = [[EMClientsharedClient].chatManagergetAllConversations];
NSLog(@"zzzzzzz %@",conversations);
self.conversations= conversations;
//显示总的未读数
[selfshowTabBarBadge];
}
-(void)showTabBarBadge{
//遍历所有的会话记录,将未读取的消息数进行累
NSIntegertotalUnreadCount =0;
for(EMConversation*conversationinself.conversations) {
totalUnreadCount += [conversationunreadMessagesCount];
}
self.navigationController.tabBarItem.badgeValue= [NSStringstringWithFormat:@"%ld",totalUnreadCount];
}
在mainstoryboard中找到会话控制器,设置cell的重用标识,cell的style为subtitle
在会话控制器中实现数据源方法
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
returnself.conversations.count;
}
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
staticNSString*ID =@"ConversationCell";
UITableViewCell*cell = [tableViewdequeueReusableCellWithIdentifier:ID];
//获取会话模型
EMConversation*conversaion =self.conversations[indexPath.row];
//显示数据
// 1.显示用户名
cell.textLabel.text= [NSStringstringWithFormat:@"%@ ====未读消息数:%zd",conversaion.conversationId,conversaion.unreadMessagesCount];
// 2.显示最新的一条记录
//获取消息体
idbody = conversaion.latestMessage.body;
if([bodyisKindOfClass:[EMTextMessageBodyclass]]) {
EMTextMessageBody*textBody = body;
cell.detailTextLabel.text= textBody.text;
}elseif([bodyisKindOfClass:[EMVoiceMessageBodyclass]]){
EMVoiceMessageBody*voiceBody = body;
cell.detailTextLabel.text= [voiceBodydisplayName];
}elseif([bodyisKindOfClass:[EMImageMessageBodyclass]]){
EMImageMessageBody*imgBody = body;
cell.detailTextLabel.text= imgBody.displayName;
}else{
cell.detailTextLabel.text=@"未知消息类型";
}
returncell;
}
实现一些代理方法:
#pragma mark - EMChatManagerDelegate
//会话列表发生变化调用
- (void)conversationListDidUpdate:(NSArray*)aConversationList{
//给数据源重新赋值
self.conversations= aConversationList;
//刷新表格
[self.tableViewreloadData];
//显示总的未读数
[selfshowTabBarBadge];
}
//收到消息
//一旦接受到消息,刷新未读消息列表
-(void)messagesDidReceive:(NSArray*)aMessages{
//更新表格
[self.tableViewreloadData];
//显示总的未读数
[selfshowTabBarBadge];
}
27.设置消息为已读
点击会话界面的cell,会跳到聊天控制器
导入聊天控制器头文件
在mainstoryboard中聊天控制器中设置storyboardIDChatPage
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath{
//进入到聊天控制器
//1.从storybaord加载聊天控制器
ChatViewController*chatVc = [[UIStoryboardstoryboardWithName:@"Main"bundle:nil]instantiateViewControllerWithIdentifier:@"ChatPage"];
//会话
EMConversation*conversation =self.conversations[indexPath.row];
//2.设置好友属性
chatVc.buddy= conversation.conversationId;
//3.展现聊天界面
[self.navigationControllerpushViewController:chatVcanimated:YES];
}
到聊天控制器中设置消息为已读
添加一个属性
/**当前会话对象*/
@property(nonatomic,strong)EMConversation*conversation;
-(void)loadLocalChatRecords{
self.conversation= conversation;
-(void)addDataSourcesWithMessage:(EMMessage*)msg{
// 3.设置消息为已读取
//将消息设置为已读
//aMessageId要设置消息的ID
//pError错误信息
[self.conversationmarkMessageAsReadWithId:msg.messageIderror:nil];