iOS开发之环信(三)---界面搭建

聊天控制器(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];

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

推荐阅读更多精彩内容

  • 概述在iOS开发中UITableView可以说是使用最广泛的控件,我们平时使用的软件中到处都可以看到它的影子,类似...
    liudhkk阅读 8,987评论 3 38
  • 1.badgeVaule气泡提示 2.git终端命令方法> pwd查看全部 >cd>ls >之后桌面找到文件夹内容...
    i得深刻方得S阅读 4,631评论 1 9
  • 上官网注册账号 首先来到环信的官网,然后登陆.没有账号先注册一个. 进去之后创建应用,如图 创建应用界面 点击确定...
    loneWolf01阅读 500评论 0 0
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,124评论 29 470
  • 看路上 那些 大大小小 缓慢爬行的“海爬狗” 看江上 那些 层层叠叠 穿梭往来的“浪里白” 看街上 那些 花花绿绿...
    闻丁阅读 521评论 3 1