聊天控制器(ChatViewController)界面搭建
14.聊天界面-工具條排版
1)搭建界面
添加聊天控制器:到mainstoryboard中找到addressbook的tableview控制器,將cell拖線給一個uiviewcontroller選擇show诊胞,在該控制器導(dǎo)航欄中間拖一個navigationitem昨稼,修改名為“聊天界面”咽袜,拖一個uiew尺寸與底部tabar一樣大小,并隱藏底部的tabar(1.點擊控制器bottombar選擇為none.2.點擊控制器勾選Hide Bottom Bar
on Push)歇由。(注意輸入框為textview),中間部分拖一個tableview,自動布局切省,設(shè)置代理,注意tableview中有個屬性帕胆,拖動時隱藏鍵盤(scrollview-keyboard-選擇dissmiss on drag)
自定義該聊天控制器:chatviewcontroller朝捆,將storyboard中控制器class改為該控制器的類名。
2)實現(xiàn)鍵盤退出懒豹、彈出芙盘,工具欄緊鏈接功能:控制器代碼如下:
//1.將工具條底部的約束拖線為該控制器的屬性。
@property(weak,nonatomic)IBOutletNSLayoutConstraint*chatInputBottomConstant;
//2.viewdidload中監(jiān)聽鍵盤彈出/回方法
//1).監(jiān)聽鍵盤彈出脸秽,把inputToolbar(輸入工具條)往上移
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(kbWillShow:)name:UIKeyboardWillShowNotificationobject:nil];
//2).監(jiān)聽鍵盤退出儒老,inputToolbar恢復(fù)原位
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(kbWillHide:)name:UIKeyboardWillHideNotificationobject:nil];
//3.實現(xiàn)監(jiān)聽方法
#pragma mark鍵盤顯示時會觸發(fā)的方法
-(void)kbWillShow:(NSNotification*)noti{
//1.獲取鍵盤高度
//1.1獲取鍵盤結(jié)束時候的位置
CGRectkbEndFrm = [noti.userInfo[UIKeyboardFrameEndUserInfoKey]CGRectValue];
CGFloatkbHeight = kbEndFrm.size.height;
//2.更改inputToolbar底部約束
self.chatInputBottomConstant.constant= kbHeight;
//添加動畫
[UIViewanimateWithDuration:0.25animations:^{
[self.viewlayoutIfNeeded];
}];
}
#pragma mark鍵盤退出時會觸發(fā)的方法
-(void)kbWillHide:(NSNotification*)noti{
//inputToolbar恢復(fù)原位
self.chatInputBottomConstant.constant=0;
}
//4.注銷監(jiān)聽者
-(void)dealloc{
[[NSNotificationCenterdefaultCenter]removeObserver:self];
}
15.聊天界面-接收方cell的排版
經(jīng)過分析,cell有三種類型:接受方cell记餐、發(fā)送方cell驮樊、時間cell。
在剛才拖入的tableview上拖一個cell重命名為receivecell片酝,再改cell中拖入控件并自動布局巩剖,給cell設(shè)置重用標(biāo)識為receivecell
注意:布局消息框:思想:底部為圖片,上面為label钠怯,先布局label佳魔,然后布局imageview,讓他與label大小一樣晦炊,然后修改約束使iamgeview周圍都比label周圍大一點鞠鲜。
A.布局label:Label布局為距離頂部15宁脊,左邊20。換行:點擊label-lines = 0贤姆、(設(shè)置label最大寬度)點擊尺子- preferred width = 242勾選榆苞,回車
B.布局uiiamgeview:cell中拖一個UIimageview,從左邊的列表中找到該imageview霞捡,拖到label的后面坐漏,同時選中l(wèi)abel、iamgeview碧信、設(shè)置四邊距都對齊約束赊琳,然后update,給iamgeview添加背景圖片砰碴,圖片拉伸:點擊imageview-streching-x:0.5/y:0.7,其余都為0躏筏。找到iamgeview的所有約束,給四周約束分別調(diào)10個間距大小呈枉。
自定義cell趁尼,view-ChatCell,將剛才storyboard中的cell的class改為該類猖辫。將上面那個label拖線到.h文件(必須)為屬性
控制器中數(shù)據(jù)源和代理的方法實現(xiàn)如下(測試而已):
-(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];
//顯示內(nèi)容
cell.chatLabel.text=@"asrqwerqwerqwerqwerqwfaskdjlfalsk;dfjalksjdflaksjdfa;sjdfklajsdkfljasfkal;sdkfjalksjdfa;jsdflkajsdf;lajskdf;las";
returncell;
}
16.聊天界面-發(fā)送方cell的排版
1).tableview中再拖一個cell酥泞,同理搭建sendcell(注意:將label脫線到cell中的同一個屬性中)
2).設(shè)置cell的高度:在自定義cell中實現(xiàn)計算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).控制器中實現(xiàn)的方法
a.添加屬性
@property(weak,nonatomic)IBOutletUITableView*tableView;
/**數(shù)據(jù)源*/
@property(nonatomic,strong)NSMutableArray*dataSources;
/**計算高度的cell工具對象(就是一個工具,給我文字長度我就能計算高度)*/
@property(nonatomic,strong)ChatCell*chatCellTool;
b.viewdidload初始化數(shù)據(jù)源
-(NSMutableArray*)dataSources{
if(!_dataSources) {
_dataSources= [NSMutableArrayarray];
}
return_dataSources;
}
//初始化數(shù)(好幾個長的)據(jù)
[self.dataSourcesaddObject:@"xcsafasdffsadfa"];
[self.dataSourcesaddObject:@"xcsafasdfsadxcssafasdfsadfafa"];
[self.dataSourcesaddObject:@"xcsafasdfxcsaadfasadfa"];
//給計算高度的cell工具對象賦值(就是初始化工具對象,也可以為sendcell)
self.chatCellTool= [self.tableViewdequeueReusableCellWithIdentifier:ReceiverCell];
c.重新實現(xiàn)數(shù)據(jù)源方法
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{
returnself.dataSources.count;
}
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
//設(shè)置label的數(shù)據(jù)
#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) {//發(fā)送方的cell
cell = [tableViewdequeueReusableCellWithIdentifier:SenderCell];
}else{//接收發(fā)方的cell
cell = [tableViewdequeueReusableCellWithIdentifier:ReceiverCell];
}
//顯示內(nèi)容
cell.chatLabel.text=self.dataSources[indexPath.row];
returncell;
}
消息管理
消息:IM交互實體啃憎,在SDK中對應(yīng)的類型是EMMessage婶博。EMMessage由EMMessageBody組成.
總結(jié):與消息相關(guān)的網(wǎng)絡(luò)請求都用到[[EMClientsharedClient].chatManager聊天管理者
獲取一個人的會話列表用EMConversation類
17.聊天界面-發(fā)送聊天消息
步驟:
在mainstoryboard中找到聊天界面的輸入框textView,點擊-return-send
監(jiān)聽該send事件:拖線該textfield的代理到聊天控制器(ChatViewController)荧飞。控制器遵守代理(UITextViewDelegate)名党,并實現(xiàn)代理方法
導(dǎo)入頭文件:#import "EMSDK.h"(用多了可以添加到pch文件中)
1).給該控制器添加一個外部屬性
//要發(fā)送人的名字
@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)中發(fā)送消息代碼如下
#pragma mark -UITextViewDelegate
-(void)textViewDidChange:(UITextView*)textView{
//監(jiān)聽Send事件--判斷最后的一個字符是不是換行字符(因為點擊send時,textfield會換行)
if([textView.texthasSuffix:@"\n"]) {
NSLog(@"發(fā)送操作");
[selfsendMessage:textView.text];
//清空textView的文字
textView.text=nil;
}
}
//發(fā)送消息
-(void)sendMessage:(NSString*)text{
//把最后一個換行字符去除
#warning換行字符只占用一個長度
text = [textsubstringToIndex:text.length-1];
//消息=消息頭+消息體
#warning每一種消息類型對象不同的消息體
//EMTextMessageBody:文字
//EMImageMessageBody:圖片
//EMLocationMessageBody:位置
//EMVoiceMessageBody:語音
//EMVideoMessageBody:視頻
//EMFileMessageBody:文件
//EMCmdMessageBody:透傳
//1.創(chuàng)建文字消息體
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;//設(shè)置為單聊消息
//消息類型為:
//EMChatTypeChat:設(shè)置為單聊消息
// EMChatTypeGroupChat:設(shè)置為群聊消息
//EMChatTypeChatRoom:設(shè)置為聊天室消息
//3.發(fā)送消息
//發(fā)送所有類型的消息都用這個接口,只是消息類型不同
[[EMClientsharedClient].chatManagersendMessage:messageprogress:^(intprogress) {
}completion:^(EMMessage*message,EMError*error) {
NSLog(@"完成消息發(fā)送%@",error);
}];
// 4.把消息添加到數(shù)據(jù)源传睹,然后再刷新表格(發(fā)送完消息立即顯示)
[self.dataSourcesaddObject:message];
[self.tableViewreloadData];
// 5.把消息顯示在頂部(發(fā)送完消息自動滾動到鍵盤頂部)
[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方法中添加當(dāng)前navigation的title為好友的名字,加載本地聊天記錄
//顯示好友的名字
self.title=self.buddy;
//加載本地數(shù)據(jù)庫聊天記錄(MessageV1)
[selfloadLocalChatRecords];
-(void)loadLocalChatRecords{
//要獲取本地聊天記錄使用會話對象
//獲取一個會話
//EMConversationTypeChat單聊會話
//EMConversationTypeGroupChat群聊會話
//EMConversationTypeChatRoom聊天室會話
EMConversation*conversation = [[EMClientsharedClient].chatManagergetConversation:self.buddytype:EMConversationTypeChatcreateIfNotExist:YES];
//加載與當(dāng)前聊天用戶所有聊天記錄
// fromUser:若傳好友名字,則只會加載好友發(fā)的聊天記錄,若為nil加載兩方的
longlongtimestamp = [[NSDatedate]timeIntervalSince1970] *1000+1;
[conversationloadMessagesWithType:EMMessageBodyTypeTexttimestamp:timestampcount:100fromUser:nilsearchDirection:EMMessageSearchDirectionUpcompletion:^(NSArray*aMessages,EMError*aError) {
//aMessages內(nèi)部為EMMessage對象
//添加到數(shù)據(jù)源
[self.dataSourcesaddObjectsFromArray:aMessages];
[self.tableViewreloadData];
}];
}
2).在自定義cellChatCell中添加外部模型屬性耳幢,并實現(xiàn)set方法,封裝
/**消息模型欧啤,內(nèi)部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的數(shù)據(jù)源方法
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{
returnself.dataSources.count;
}
-(CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{
//設(shè)置label的數(shù)據(jù)
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{//發(fā)送方
cell = [tableViewdequeueReusableCellWithIdentifier:SenderCell];
}
//顯示內(nèi)容
cell.message= message;
returncell;
}
29.聊天界面-監(jiān)聽消息回復(fù)(將對方發(fā)的消息及時顯示在當(dāng)前聊天界面)
聊天控制器中的viewdidload中設(shè)置聊天管理器代理并遵守協(xié)議EMChatManagerDelegate
[[EMClientsharedClient].chatManageraddDelegate:selfdelegateQueue:nil];
//實現(xiàn)代理方法
#pragma mark -EMChatManagerDelegate
//收到一條以上消息
- (void)messagesDidReceive:(NSArray*)aMessages{
for(EMMessage*messageinaMessages) {
#warning from一定等于當(dāng)前聊天用戶才可以刷新數(shù)據(jù)(當(dāng)test1 - test7兩者聊天時睛藻,test8發(fā)過來的消息不能顯示在該聊天記錄界面)
if([message.fromisEqualToString:self.buddy]) {
//1.把接收的消息添加到數(shù)據(jù)源
[self.dataSourcesaddObject:message];
//2.刷新表格
[self.tableViewreloadData];
//3.顯示數(shù)據(jù)到底部
[selfscrollToBottom];
}
}
}
20.完善聊天輸入框
實現(xiàn)輸入框自動改變高度:
1).找到mainstoryboard中的聊天控制器界面的ChatInputToolBar找到他的高度屬性,并拖線到聊天控制器成為其屬性
@property(weak,nonatomic)IBOutletNSLayoutConstraint*inputbarHeightConstant;
2).在聊天控制器ChatViewController中的監(jiān)聽文字改變的方法中添加:
#pragma mark -UITextViewDelegate
-(void)textViewDidChange:(UITextView*)textView{
//監(jiān)視textView.contentOffset的變化
NSLog(@"contentOffset %@",NSStringFromCGPoint(textView.contentOffset));
// 1.計算TextView的高度邢隧,調(diào)整整個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.監(jiān)聽Send事件--判斷最后的一個字符是不是換行字符(因為點擊send時店印,textfield會換行)
if([textView.texthasSuffix:@"\n"]) {
NSLog(@"發(fā)送操作");
[selfsendMessage:textView.text];
//清空textView的文字
textView.text=nil;
//發(fā)送時,textViewH的高度為33
textViewH = minHeight;
}
// 3.調(diào)整整個InputToolBar高度
self.inputbarHeightConstant.constant=6+7+ textViewH;
//加個動畫
[UIViewanimateWithDuration:0.25animations:^{
[self.viewlayoutIfNeeded];
}];
// 4.記光標(biāo)回到原位
#warning技巧
[textViewsetContentOffset:CGPointZeroanimated:YES];
[textViewscrollRangeToVisible:textView.selectedRange];
}
3).給textview添加背景:
在textview后面加一個背景圖片(imageview)倒慧,在mainstoryboard中找到ChatInputToolBar按摘,往里面拖一個imageview包券,設(shè)置其約束與textview一樣,添加背景圖片(并設(shè)置拉伸效果)炫贤,將textView的背景色改為透明
21.發(fā)送語音
1).點擊左邊的話筒按鈕溅固,輸入框變?yōu)榘l(fā)語音框,自動布局
在mainstoryboard中的聊天控制器界面的ChatInputToolBar中拖一個button兰珍,直接蓋住textView上面侍郭,修改文字為“按住說話”,修改高亮?xí)r的文字為“松開發(fā)送”掠河,設(shè)置約束亮元,固定高度,選擇默認為隱藏口柳,拖線監(jiān)聽左邊的話筒按鈕:
//語音框
@property(weak,nonatomic)IBOutletUIButton*recordBtn;
//監(jiān)聽語音點擊按鈕
- (IBAction)voiceAction:(id)sender {
//1.顯示錄音按鈕
self.recordBtn.hidden= !self.recordBtn.hidden;
self.textView.hidden= !self.textView.hidden;
}
2).到環(huán)信官方demo中EaseUI-中找到DeviceHelper導(dǎo)入框架,EaseLocalDefine.h/EaseUIResource.bundle也導(dǎo)入框架
推進項目的Lib文件苹粟。再改控制器中導(dǎo)入頭文件#import"EMCDDeviceManager.h"
拖線監(jiān)聽語音框,監(jiān)聽按下去時跃闹,動作(選中該按鈕嵌削,),找到點下觸發(fā)望艺,拖線監(jiān)聽:
#pragma mark按鈕點下去開始錄音
//開始錄音
- (IBAction)beginVoiceAction:(id)sender {
//文件名以時間命名(根據(jù)時間定義文件的名字)
//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手指從按鈕范圍內(nèi)松開結(jié)束錄音
//結(jié)束錄音
- (IBAction)endVoiceAction:(id)sender {
[[EMCDDeviceManagersharedInstance]asyncStopRecordingWithCompletion:^(NSString*recordPath,NSIntegeraDuration,NSError*error) {
//recordPath:錄音的路徑
//aDuration:錄音的時長
if(!error) {
NSLog(@"錄音成功");
NSLog(@"%@",recordPath);
//發(fā)送語音給服務(wù)器
[selfsendVoice:recordPathduration:aDuration];
}else{
NSLog(@"== %@",error);
}
}];
}
#pragma mark手指從按鈕外面松開取消錄音
//取消錄音
- (IBAction)cancelVoiceAction:(id)sender {
[[EMCDDeviceManagersharedInstance]cancelCurrentRecording];
}
#pragma mark發(fā)送語音消息
//發(fā)送語音
-(void)sendVoice:(NSString*)recordPath duration:(NSInteger)duration{
//1.創(chuàng)建語音消息體
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;//設(shè)置為單聊消息
//3.發(fā)送消息
//發(fā)送所有類型的消息都用這個接口,只是消息類型不同
[[EMClientsharedClient].chatManagersendMessage:voiceprogress:^(intprogress) {
}completion:^(EMMessage*message,EMError*error) {
NSLog(@"完成消息發(fā)送%@",error);
}];
// 4.把消息添加到數(shù)據(jù)源,然后再刷新表格(發(fā)送完消息立即顯示)
[self.dataSourcesaddObject:voice];
[self.tableViewreloadData];
// 5.把消息顯示在頂部(發(fā)送完消息自動滾動到鍵盤頂部)
[selfscrollToBottom];
}
22.播放語音
1).//顯示語音的形式為圖片加文字找默,在ChatCell中實現(xiàn)方法:
#pragma mark返回語音富文本
-(NSAttributedString*)voiceAtt{
//創(chuàng)建一個可變的富文本
NSMutableAttributedString*voiceAttM = [[NSMutableAttributedStringalloc]init];
// 1.接收方:富文本=圖片+時間
if([self.reuseIdentifierisEqualToString:ReceiverCell]) {
// 1.1接收方的語音圖片
UIImage*receiverImg = [UIImageimageNamed:@"chat_receiver_audio_playing_full"];
// 1.2創(chuàng)建圖片附件
NSTextAttachment*imgAttachment = [[NSTextAttachmentalloc]init];
imgAttachment.image= receiverImg;
imgAttachment.bounds=CGRectMake(0, -7,30,30);
// 1.3圖片富文本
NSAttributedString*imgAtt = [NSAttributedStringattributedStringWithAttachment:imgAttachment];
[voiceAttMappendAttributedString:imgAtt];
// 1.4.創(chuàng)建時間富文本
//獲取時間
EMVoiceMessageBody*voiceBody = (EMVoiceMessageBody*)self.message.body;
NSIntegerduration = voiceBody.duration;
NSString*timeStr = [NSStringstringWithFormat:@"%ld'",duration];
NSAttributedString*timeAtt = [[NSAttributedStringalloc]initWithString:timeStr];
[voiceAttMappendAttributedString:timeAtt];
}else{
// 2.發(fā)送方:富文本=時間+圖片
// 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"];
//創(chuàng)建圖片附件
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).給語音消息添加點擊手勢艇劫,并監(jiān)聽方法:(在mainstoryboard中找到,消息label打開用戶交互)
//導(dǎo)入頭文件:#import"EMCDDeviceManager.h"#import"AudioPlayTool.h"
-(void)awakeFromNib{
//在此方法做一些初始化操作
// 1.給label添加敲擊手勢
UITapGestureRecognizer*tap = [[UITapGestureRecognizeralloc]initWithTarget:selfaction:@selector(messageLabelTap:)];
[self.chatLabeladdGestureRecognizer:tap];
}
#pragma mark messagelabel點擊的觸發(fā)方法
-(void)messageLabelTap:(UITapGestureRecognizer*)recognizer{
NSLog(@"%s",__func__);
//播放語音
//只有當(dāng)前的類型是為語音的時候才播放
//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;//正在執(zhí)行動畫的ImageView
@implementationAudioPlayTool
+(void)playWithMessage:(EMMessage*)msg msgLabel:(UILabel*)msgLabel receiver:(BOOL)receiver{
//把以前的動畫移除
[animatingImageViewstopAnimating];
[animatingImageViewremoveFromSuperview];
//1.播放語音
//獲取語音路徑
EMVoiceMessageBody*voiceBody = (EMVoiceMessageBody*) msg.body;
// localPath:本地音頻路徑
// remotePath:服務(wù)器音頻路徑
//本地語音文件路徑
NSString*path = voiceBody.localPath;
//如果本地語音文件不存在惩激,使用服務(wù)器語音
NSFileManager*manager = [NSFileManagerdefaultManager];
if(![managerfileExistsAtPath:path]) {
path = voiceBody.remotePath;
}
//播放語音
[[EMCDDeviceManagersharedInstance]asyncPlayingWithPath:pathcompletion:^(NSError*error) {
NSLog(@"語音播放完成%@",error);
//移除動畫
[animatingImageViewstopAnimating];
[animatingImageViewremoveFromSuperview];
}];
//2.添加動畫(點擊語音消息時有動畫)讓UIImageView播放動畫店煞、動畫的內(nèi)部實現(xiàn)為圖片的輪播
//2.1創(chuàng)建一個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:當(dāng)輸入多行文字時,再點語音按鈕风钻,此時的textView欄不恢復(fù)原來高度顷蟀,則:
在聊天控制器XMGChatViewController中的
//監(jiān)聽語音點擊按鈕
- (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{
//當(dāng)不錄音的時候,鍵盤顯示
[self.textViewbecomeFirstResponder];
//恢復(fù)InputToolBar高度(輸入幾行文字骡技,點擊語音按鈕鸣个,再點會文字按鈕,textView高度不變)
[selftextViewDidChange:self.textView];
}
}
23.發(fā)送圖片顯示圖片
A.發(fā)送圖片
1).點擊輸入框的右邊的+按鈕布朦,到相冊選擇圖片
拖線到聊天控制器中監(jiān)聽方法:
- (IBAction)showImgPickerAction:(id)sender {
//顯示圖片選擇的控制器
UIImagePickerController*imgPicker = [[UIImagePickerControlleralloc]init];
//設(shè)置源
imgPicker.sourceType=UIImagePickerControllerSourceTypePhotoLibrary;
imgPicker.delegate=self;
[selfpresentViewController:imgPickeranimated:YEScompletion:NULL];
/**用戶選中圖片的回調(diào)(代理方法)*/
-(void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
//1.獲取用戶選中的圖片
UIImage*selectedImg =info[UIImagePickerControllerOriginalImage];
//2.發(fā)送圖片
[selfsendImg:selectedImg];
//3.隱藏當(dāng)前圖片選擇控制器
[selfdismissViewControllerAnimated:YEScompletion:NULL];
}
}
#pragma mark發(fā)送圖片
-(void)sendImg:(UIImage*)selectedImg{
//1.構(gòu)造圖片消息體
/*
*第一個參數(shù):原始大小的圖片對象1000 * 1000
*第二個參數(shù):縮略圖的圖片對象120 * 120
*/
//將image轉(zhuǎn)化為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;//設(shè)置為單聊消息
//3.發(fā)送消息
//發(fā)送所有類型的消息都用這個接口,只是消息類型不同
[[EMClientsharedClient].chatManagersendMessage:imageMessageprogress:^(intprogress) {
}completion:^(EMMessage*message,EMError*error) {
NSLog(@"完成消息發(fā)送%@",error);
}];
// 4.把消息添加到數(shù)據(jù)源囤萤,然后再刷新表格(發(fā)送完消息立即顯示)
[self.dataSourcesaddObject:imageMessage];
[self.tableViewreloadData];
// 5.把消息顯示在頂部(發(fā)送完消息自動滾動到鍵盤頂部)
[selfscrollToBottom];
}
2).抽取方法(把發(fā)送文本的方法名改為sendText,由于發(fā)文本、圖片是趴、語音都有這一個段代碼涛舍,因此抽成一個方法)
-(void)sendMessage:(EMMessageBody*)body{
//1.生成Message消息對象
NSString*from = [[EMClientsharedClient]currentUsername];
EMMessage*message = [[EMMessagealloc]initWithConversationID:self.buddyfrom:fromto:self.buddybody:bodyext:nil];
message.chatType=EMChatTypeChat;//設(shè)置為單聊消息
//消息類型為:
//EMChatTypeChat:設(shè)置為單聊消息
// EMChatTypeGroupChat:設(shè)置為群聊消息
//EMChatTypeChatRoom:設(shè)置為聊天室消息
//2.發(fā)送消息
//發(fā)送所有類型的消息都用這個接口,只是消息類型不同
[[EMClientsharedClient].chatManagersendMessage:messageprogress:^(intprogress) {
}completion:^(EMMessage*message,EMError*error) {
NSLog(@"完成消息發(fā)送%@",error);
}];
// 3.把消息添加到數(shù)據(jù)源,然后再刷新表格(發(fā)送完消息立即顯示)
[self.dataSourcesaddObject:message];
[self.tableViewreloadData];
// 4.把消息顯示在頂部(發(fā)送完消息自動滾動到鍵盤頂部)
[selfscrollToBottom];
}
B.顯示圖片
拖入SDWebImage框架唆途,導(dǎo)入#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};
//設(shè)置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.設(shè)置圖片控件為縮略圖的尺寸
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方法,網(wǎng)絡(luò)路徑使用URLWithString:
[self.chatImgViewsd_setImageWithURL:[NSURLfileURLWithPath:imgBody.thumbnailLocalPath]placeholderImage:palceImg];
}else{
//如果本地圖片不存吹榴,從網(wǎng)絡(luò)加載圖片
[self.chatImgViewsd_setImageWithURL:[NSURLURLWithString:imgBody.thumbnailRemotePath]placeholderImage:palceImg];
}
}
C.修改bug
1).圖片cell會重用亭敢,重用的時候應(yīng)該講圖片移除;在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];
}
在聊天控制器中調(diào)用:
-(void)scrollViewWillBeginDragging:(UIScrollView*)scrollView{
//停止語音播放
[AudioPlayToolstop];
}
24.顯示時間的cell
時間顯示的規(guī)則
同一分中內(nèi)的消息帅刀,只顯示一個時間
/*
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拖線為屬性。設(shè)置重用標(biāo)識瓜晤。
分析:數(shù)據(jù)源數(shù)組中不僅有模型對象锥余,也有時間字符串對象,因此痢掠,在數(shù)據(jù)源實現(xiàn)cell方法中驱犹,導(dǎo)入TimeCell頭文件添加
//判斷數(shù)據(jù)源類型
if([self.dataSources[indexPath.row]isKindOfClass:[NSStringclass]]) {//顯示時間cell
XMGTimeCell*timeCell = [tableViewdequeueReusableCellWithIdentifier:@"TimeCell"];
timeCell.timeLabel.text=self.dataSources[indexPath.row];
returntimeCell;
}
在cell高度數(shù)據(jù)源方法中設(shè)置時間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.獲取當(dāng)前的時間
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.獲取消息發(fā)送時間
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數(shù)組需要判斷什么時間添加模型医舆,什么時間添加時間字符串因此寫一個獨立的方法:在聊天控制器中修改
/**當(dāng)前添加的時間*/
@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];
}
修改加載數(shù)據(jù)方法:
-(void)loadLocalChatRecords{
//假設(shè)在數(shù)組的第一位置添加時間
//[self.dataSources addObject:@"16:06"];
//要獲取本地聊天記錄使用會話對象
EMConversation*conversation = [[EaseMobsharedInstance].chatManagerconversationForChatter:self.buddy.usernameconversationType:eConversationTypeChat];
//加載與當(dāng)前聊天用戶所有聊天記錄
NSArray*messages = [conversationloadAllMessages];
//for (id obj in messages) {
//NSLog(@"%@",[obj class]);
//}
//添加到數(shù)據(jù)源
//[self.dataSources addObjectsFromArray:messages];
for(EMMessage*msgObjinmessages) {
[selfaddDataSourcesWithMessage:msgObj];
}
}
修改聊天控制器中添加數(shù)據(jù)源數(shù)組方法:
-(void)sendMessage:(id)body{
[selfaddDataSourcesWithMessage:msgObj];
-(void)didReceiveMessage:(EMMessage*)message{
//1.把接收的消息添加到數(shù)據(jù)源
//[self.dataSources addObject:message];
[selfaddDataSourcesWithMessage:message];
27.顯示歷史會話記錄(就是顯示給誰聊過天)
在會話控制器XMGConversationViewController中
/**歷史會話記錄*/
@property(nonatomic,strong)NSArray*conversations;
viewDidLoad添加:
//添加聊天管理者代理
[[EMClientsharedClient].chatManageraddDelegate:selfdelegateQueue:nil];
//獲取歷史會話記錄
[selfloadConversations];
-(void)loadConversations{
//獲取歷史會話記錄
//1.從內(nèi)存中獲取歷史回話記錄,獲取內(nèi)存中所有會話
//獲取所有會話,如果內(nèi)存中不存在會從DB中加載
NSArray*conversations = [[EMClientsharedClient].chatManagergetAllConversations];
NSLog(@"zzzzzzz %@",conversations);
self.conversations= conversations;
//顯示總的未讀數(shù)
[selfshowTabBarBadge];
}
-(void)showTabBarBadge{
//遍歷所有的會話記錄象缀,將未讀取的消息數(shù)進行累
NSIntegertotalUnreadCount =0;
for(EMConversation*conversationinself.conversations) {
totalUnreadCount += [conversationunreadMessagesCount];
}
self.navigationController.tabBarItem.badgeValue= [NSStringstringWithFormat:@"%ld",totalUnreadCount];
}
在mainstoryboard中找到會話控制器蔬将,設(shè)置cell的重用標(biāo)識,cell的style為subtitle
在會話控制器中實現(xiàn)數(shù)據(jù)源方法
#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];
//顯示數(shù)據(jù)
// 1.顯示用戶名
cell.textLabel.text= [NSStringstringWithFormat:@"%@ ====未讀消息數(shù):%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;
}
實現(xiàn)一些代理方法:
#pragma mark - EMChatManagerDelegate
//會話列表發(fā)生變化調(diào)用
- (void)conversationListDidUpdate:(NSArray*)aConversationList{
//給數(shù)據(jù)源重新賦值
self.conversations= aConversationList;
//刷新表格
[self.tableViewreloadData];
//顯示總的未讀數(shù)
[selfshowTabBarBadge];
}
//收到消息
//一旦接受到消息,刷新未讀消息列表
-(void)messagesDidReceive:(NSArray*)aMessages{
//更新表格
[self.tableViewreloadData];
//顯示總的未讀數(shù)
[selfshowTabBarBadge];
}
27.設(shè)置消息為已讀
點擊會話界面的cell,會跳到聊天控制器
導(dǎo)入聊天控制器頭文件
在mainstoryboard中聊天控制器中設(shè)置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.設(shè)置好友屬性
chatVc.buddy= conversation.conversationId;
//3.展現(xiàn)聊天界面
[self.navigationControllerpushViewController:chatVcanimated:YES];
}
到聊天控制器中設(shè)置消息為已讀
添加一個屬性
/**當(dāng)前會話對象*/
@property(nonatomic,strong)EMConversation*conversation;
-(void)loadLocalChatRecords{
self.conversation= conversation;
-(void)addDataSourcesWithMessage:(EMMessage*)msg{
// 3.設(shè)置消息為已讀取
//將消息設(shè)置為已讀
//aMessageId要設(shè)置消息的ID
//pError錯誤信息
[self.conversationmarkMessageAsReadWithId:msg.messageIderror:nil];