美文网首页
iOS开发之环信(三)---界面搭建

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

作者: 夜空已沉寂 | 来源:发表于2016-10-14 09:50 被阅读1686次

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

    相关文章

      网友评论

          本文标题:iOS开发之环信(三)---界面搭建

          本文链接:https://www.haomeiwen.com/subject/otugyttx.html