任务
1.完善输入框
2.发语音
3.发图片
4.聊天的时间显示
5.历史的会话记录
01.完善聊天输入框
打开 XMGChatViewController.m
在延展中加入
/** InputToolBar 高度的约束*/
@property (weak,nonatomic) IBOutlet NSLayoutConstraint *inputToolBarHegihtConstraint;
修改 textViewDidChange 方法如下:
#pragma mark - UITextView代理
-(void)textViewDidChange:(UITextView *)textView{
NSLog(@"contentOffset %@",NSStringFromCGPoint(textView.contentOffset));
// 1.计算TextView的高度,
CGFloat textViewH = 0;
CGFloat minHeight = 33;//textView最小的高度
CGFloat maxHeight = 68;//textView最大的高度
// 获取contentSize的高度
CGFloat contentHeight = textView.contentSize.height;
if (contentHeight < minHeight) {
textViewH = minHeight;
}elseif (contentHeight > maxHeight){
textViewH = maxHeight;
}else{
textViewH = contentHeight;
}
// 2.监听Send事件--判断最后的一个字符是不是换行字符
if ([textView.text hasSuffix:@"\n"]) {
NSLog(@"发送操作");
[self sendMessage:textView.text];
// 清空textView的文字
textView.text = nil;
// 发送时,textViewH的高度为33
textViewH = minHeight;
}
// 3.调整整个InputToolBar高度
self.inputToolBarHegihtConstraint.constant =6 + 7 + textViewH;
// 加个动画
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
}];
// 4.记光标回到原位
#warning 技巧
[textView setContentOffset:CGPointZero animated:YES];
[textView scrollRangeToVisible:textView.selectedRange];
}
打开 Main.storyboard
找到 XMGChatViewController 控制器
选中 ChatInputToolBar 找到其约束 Heigh Equals:46 连接输出口 inputToolBarHegihtConstraint
在 ChatInputToolBar 视图 中添加 imageView 添加到第一个控件位置
设置 label:textViewBg
image:chatBar_textBg
Stretching x y width height:0.5 0.5 0 0
x y width height:43 6 196 33
设置 约束 勾选 上下左右
选中 textView background:default
运行
02.发送语音
在录音前导入环信封装的两个录音框架
参看PPT-22
将 iOSSDK-20160202/ChatDemo-UI2.0/3rdparty下面的 DeviceUtil、VoiceConvert添加到工程lib目录下
刚才导入的两个框架,已经实现了录音Api
打开 XMGChatViewController.m
加入 #import"EMCDDeviceManager.h"
在延展中加入
@property (weak,nonatomic) IBOutlet UIButton *recordBtn;
在类中加入方法
#pragma mark 发送语音消息
-(void)sendVoice:(NSString *)recordPath duration:(NSInteger)duration{
// 1.构造一个语音消息体
EMChatVoice *chatVoice = [[EMChatVoice alloc] initWithFile:recordPath displayName:@"[语音]"];
// chatVoice.duration = duration;
EMVoiceMessageBody *voiceBody = [[EMVoiceMessageBody alloc] initWithChatObject:chatVoice];
voiceBody.duration = duration;
// 2.构造一个消息对象
EMMessage *msgObj = [[EMMessage alloc] initWithReceiver:self.buddy.username bodies:@[voiceBody]];
//聊天的类型单聊
msgObj.messageType = eMessageTypeChat;
// 3.发送
[[EaseMob sharedInstance].chatManager asyncSendMessage:msgObj progress:nil prepare:^(EMMessage *message, EMError *error) {
NSLog(@"准备发送语音");
} onQueue:nil completion:^(EMMessage *message, EMError *error) {
if (!error) {
NSLog(@"语音发送成功");
}else{
NSLog(@"语音发送失败");
}
} onQueue:nil];
// 3.把消息添加到数据源,然后再刷新表格
[self.dataSources addObject:msgObj];
[self.tableView reloadData];
// 4.把消息显示在顶部
[self scrollToBottom];
}
#pragma mark - Action
- (IBAction)voiceAction:(UIButton *)sender {
// 1.显示录音按钮
self.recordBtn.hidden = !self.recordBtn.hidden;
}
#pragma mark 按钮点下去开始录音
- (IBAction)beginRecordAction:(id)sender {
// 文件名以时间命名
int x = arc4random() %100000;
NSTimeInterval time = [[NSDate date] timeIntervalSince1970];
NSString *fileName = [NSString stringWithFormat:@"%d%d",(int)time,x];
NSLog(@"按钮点下去开始录音");
[[EMCDDeviceManager sharedInstance] asyncStartRecordingWithFileName:fileName completion:^(NSError *error) {
if (!error) {
NSLog(@"开始录音成功");
}
}];
}
#pragma mark 手指从按钮范围内松开结束录音
- (IBAction)endRecordAction:(id)sender {
NSLog(@"手指从按钮松开结束录音");
[[EMCDDeviceManager sharedInstance] asyncStopRecordingWithCompletion:^(NSString *recordPath, NSInteger aDuration, NSError *error) {
if (!error) {
NSLog(@"录音成功");
NSLog(@"%@",recordPath);
// 发送语音给服务器
[self sendVoice:recordPath duration:aDuration];
}else{
NSLog(@"== %@",error);
}
}];
}
#pragma mark 手指从按钮外面松开取消录音
- (IBAction)cancelRecordAction:(id)sender {
[[EMCDDeviceManager sharedInstance] cancelCurrentRecording];
}
打开 Main.storyboard
找到 XMGChatViewController 控制器
在 ChatInputToolBar 视图 中添加 button title:按住说话
label:recordBtn
Text Color:White Color
Background: Dark Gray Color
State Config : Highlighted 松开发送
x y width height:43 5 196 33
约束 勾选 左右下 5 5 8
勾选 Height:33
勾选 Hidden
连接输出口 recordBtn
事件
beginRecordAction — recordBtn (Touch Down)
cancelRecordAction — recordBtn (Touch Up Outside)
endRecordAction — recordBtn (Touch Up Inside)
voiceAction — recordBtn (Touch Up Inside)
运行
查看沙盒Library/appdata/chatbuffer下是否生成 .amr文件
03.播放语音
右键 Chat【聊天】 — new class name:XMGAudioPlayTool
superclass:NSObject
打开 XMGAudioPlayTool.h
加入 #import"EaseMob.h"
在加入
+(void)playWithMessage:(EMMessage *)msg msgLabel:(UILabel *)msgLabel receiver:(BOOL)receiver;
打开 XMGAudioPlayTool.m
加入
#import "EMCDDeviceManager.h"
static UIImageView *animatingImageView;//正在执行动画的ImageView
在加入
+(void)playWithMessage:(EMMessage *)msg msgLabel:(UILabel *)msgLabel receiver:(BOOL)receiver{
// 把以前的动画移除
[animatingImageView stopAnimating];
[animatingImageView removeFromSuperview];
//1.播放语音
//获取语音路径
EMVoiceMessageBody *voiceBody = msg.messageBodies[0];
// 本地语音文件路径
NSString *path = voiceBody.localPath;
// 如果本地语音文件不存在,使用服务器语音
NSFileManager *manager = [NSFileManager defaultManager];
if (![manager fileExistsAtPath:path]) {
path = voiceBody.remotePath;
}
[[EMCDDeviceManager sharedInstance] asyncPlayingWithPath:path completion:^(NSError *error) {
NSLog(@"语音播放完成 %@",error);
// 移除动画
[animatingImageView stopAnimating];
[animatingImageView removeFromSuperview];
}];
//2.添加动画
//2.1创建一个UIImageView添加到Label上
UIImageView *imgView = [[UIImageView alloc] init];
[msgLabel addSubview:imgView];
//2.2添加动画图片
if (receiver) {
imgView.animationImages = @[[UIImage imageNamed:@"chat_receiver_audio_playing000"],
[UIImage imageNamed:@"chat_receiver_audio_playing001"],
[UIImage imageNamed:@"chat_receiver_audio_playing002"],
[UIImage imageNamed:@"chat_receiver_audio_playing003"]];
imgView.frame = CGRectMake(0,0, 30,30);
}else{
imgView.animationImages = @[[UIImage imageNamed:@"chat_sender_audio_playing_000"],
[UIImage imageNamed:@"chat_sender_audio_playing_001"],
[UIImage imageNamed:@"chat_sender_audio_playing_002"],
[UIImage imageNamed:@"chat_sender_audio_playing_003"]];
imgView.frame = CGRectMake(msgLabel.bounds.size.width - 30, 0,30, 30);
}
imgView.animationDuration = 1;
[imgView startAnimating];
animatingImageView = imgView;
}
打开 XMGChatCell.m
加入
#import "EMCDDeviceManager.h"
#import "XMGAudioPlayTool.h"
修改 setMessage 方法如下
-(void)awakeFromNib{
// 在此方法做一些初始化操作
// 1.给label添加敲击手势
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(messageLabelTap:)];
[self.messageLabel addGestureRecognizer:tap];
}
#pragma mark messagelabel 点击的触发方法
-(void)messageLabelTap:(UITapGestureRecognizer *)recognizer{
NSLog(@"%s",__func__);
//播放语音
//只有当前的类型是为语音的时候才播放
//1.获取消息体
id body =self.message.messageBodies[0];
if ([body isKindOfClass:[EMVoiceMessageBodyclass]]) {
NSLog(@"播放语音");
BOOL receiver = [self.reuseIdentifier isEqualToString:ReceiverCell];
[XMGAudioPlayTool playWithMessage:self.message msgLabel:self.messageLabel receiver:receiver];
}
// EMTextMessageBody
// EMVoiceMessageBody
// EMImageMessageBody
}
-(void)setMessage:(EMMessage *)message{
_message = message;
// 1.获取消息体
id body = message.messageBodies[0];
if ([body isKindOfClass:[EMTextMessageBodyclass]]) {//文本消息
EMTextMessageBody *textBody = body;
self.messageLabel.text = textBody.text;
}elseif([body isKindOfClass:[EMVoiceMessageBodyclass]]){//语音消息
// self.messageLabel.text = @"【语音】";
self.messageLabel.attributedText = [self voiceAtt];
}
else{
self.messageLabel.text =@"未知类型";
}
}
#pragma mark 返回语音富文本
-(NSAttributedString *)voiceAtt{
// 创建一个可变的富文本
NSMutableAttributedString *voiceAttM = [[NSMutableAttributedString alloc] init];
// 1.接收方:富文本=图片 +时间
if ([self.reuseIdentifier isEqualToString:ReceiverCell]) {
// 1.1接收方的语音图片
UIImage *receiverImg = [UIImage imageNamed:@"chat_receiver_audio_playing_full"];
// 1.2创建图片附件
NSTextAttachment *imgAttachment = [[NSTextAttachment alloc] init];
imgAttachment.image = receiverImg;
imgAttachment.bounds = CGRectMake(0, -7,30, 30);
// 1.3图片富文本
NSAttributedString *imgAtt = [NSAttributedString attributedStringWithAttachment:imgAttachment];
[voiceAttM appendAttributedString:imgAtt];
// 1.4.创建时间富文本
// 获取时间
EMVoiceMessageBody *voiceBody = self.message.messageBodies[0];
NSInteger duration = voiceBody.duration;
NSString *timeStr = [NSString stringWithFormat:@"%ld'",duration];
NSAttributedString *timeAtt = [[NSAttributedString alloc] initWithString:timeStr];
[voiceAttM appendAttributedString:timeAtt];
}else{
// 2.发送方:富文本=时间 +图片
// 2.1 拼接时间
// 获取时间
EMVoiceMessageBody *voiceBody = self.message.messageBodies[0];
NSInteger duration = voiceBody.duration;
NSString *timeStr = [NSString stringWithFormat:@"%ld'",duration];
NSAttributedString *timeAtt = [[NSAttributedString alloc] initWithString:timeStr];
[voiceAttM appendAttributedString:timeAtt];
// 2.1拼接图片
UIImage *receiverImg = [UIImage imageNamed:@"chat_sender_audio_playing_full"];
// 创建图片附件
NSTextAttachment *imgAttachment = [[NSTextAttachment alloc] init];
imgAttachment.image = receiverImg;
imgAttachment.bounds = CGRectMake(0, -7,30, 30);
// 图片富文本
NSAttributedString *imgAtt = [NSAttributedString attributedStringWithAttachment:imgAttachment];
[voiceAttM appendAttributedString:imgAtt];
}
return [voiceAttM copy];
}
打开 XMGChatViewController.m
在延展中加入
@property (weak,nonatomic) IBOutlet UITextView *textView;
修改 voiceAction 方法如下
- (IBAction)voiceAction:(UIButton *)sender {
// 1.显示录音按钮
self.recordBtn.hidden = !self.recordBtn.hidden;
if (self.recordBtn.hidden ==NO) {//录音按钮要显示
//InputToolBar 的高度要回来默认(46);
self.inputToolBarHegihtConstraint.constant =46;
// 隐藏键盘
[self.view endEditing:YES];
}else{
//当不录音的时候,键盘显示
[self.textView becomeFirstResponder];
// 恢复InputToolBar高度
[self textViewDidChange:self.textView];
}
}
在 sendMessage 方法顶上加入
#pragma mark 发送文本消息
打开 Main.storyboard
找到 XMGChatViewController 控制器
连接 textView 输出口
运行
04.发送图片显示图片
将课件中的 SDWebImage 添加到工程Lib目录下
打开 XMGChatCell.m
加入 #import"UIImageView+WebCache.h"
在 setMessage方法中的
else if([body isKindOfClass:[EMVoiceMessageBodyclass]]){//语音消息
// self.messageLabel.text = @"【语音】";
self.messageLabel.attributedText = [self voiceAtt];
}
下面加入
else if([body isKindOfClass:[EMImageMessageBodyclass]]){//图片消息
[self showImage];
}
在类中加入方法
-(void)showImage{
// 获取图片消息体
EMImageMessageBody *imgBody = self.message.messageBodies[0];
CGRect thumbnailFrm = (CGRect){0,0,imgBody.thumbnailSize};
// 设置Label的尺寸足够显示UIImageView
NSTextAttachment *imgAttach = [[NSTextAttachment alloc] init];
imgAttach.bounds = thumbnailFrm;
NSAttributedString *imgAtt = [NSAttributedString attributedStringWithAttachment:imgAttach];
self.messageLabel.attributedText = imgAtt;
//1.cell里添加一个UIImageView
UIImageView *chatImgView = [[UIImageView alloc] init];
[self.messageLabel addSubview:chatImgView];
chatImgView.backgroundColor = [UIColor redColor];
//2.设置图片控件为缩略图的尺寸
chatImgView.frame = thumbnailFrm;
//3.下载图片
NSLog(@"thumbnailLocalPath %@",imgBody.thumbnailLocalPath);
NSLog(@"thumbnailRemotePath %@",imgBody.thumbnailRemotePath);
NSFileManager *manager = [NSFileManager defaultManager];
// 如果本地图片存在,直接从本地显示图片
UIImage *palceImg = [UIImage imageNamed:@"downloading"];
if ([manager fileExistsAtPath:imgBody.thumbnailLocalPath]) {
#warning 本地路径使用fileURLWithPath方法
[chatImgView sd_setImageWithURL:[NSURL fileURLWithPath:imgBody.thumbnailLocalPath] placeholderImage:palceImg];
}else{
// 如果本地图片不存,从网络加载图片
[chatImgView sd_setImageWithURL:[NSURL URLWithString:imgBody.thumbnailRemotePath] placeholderImage:palceImg];
}
}
打开 XMGChatViewController.m
在延展中加入
,UIImagePickerControllerDelegate,UINavigationControllerDelegate
在类中加入方法
#pragma mark 发送图片
-(void)sendImg:(UIImage *)selectedImg{
//1.构造图片消息体
/*
* 第一个参数:原始大小的图片对象 1000 * 1000
* 第二个参数:缩略图的图片对象 120 * 120
*/
EMChatImage *orginalChatImg = [[EMChatImage alloc] initWithUIImage:selectedImg displayName:@"【图片】"];
EMImageMessageBody *imgBody = [[EMImageMessageBody alloc] initWithImage:orginalChatImg thumbnailImage:nil];
[self sendMessage:imgBody];
}
-(void)sendMessage:(id<IEMMessageBody>)body{
//1.构造消息对象
EMMessage *msgObj = [[EMMessage alloc] initWithReceiver:self.buddy.username bodies:@[body]];
msgObj.messageType = eMessageTypeChat;
//2.发送消息
[[EaseMob sharedInstance].chatManager asyncSendMessage:msgObj progress:nil prepare:^(EMMessage *message, EMError *error) {
NSLog(@"准备发送图片");
} onQueue:nil completion:^(EMMessage *message, EMError *error) {
NSLog(@"图片发送成功 %@",error);
} onQueue:nil];
// 3.把消息添加到数据源,然后再刷新表格
[self.dataSources addObject:msgObj];
[self.tableView reloadData];
// 4.把消息显示在顶部
[self scrollToBottom];
}
将 sendMessage方法名字改为 sendText
将 textViewDidChange 方法中的 sendMessage方法名字改为 sendText
修改 sendText、sendVoice 2个方法如下
#pragma mark 发送文本消息
-(void)sendText:(NSString *)text{
// 把最后一个换行字符去除
#warning 换行字符只占用一个长度
text = [text substringToIndex:text.length - 1];
// 创建一个聊天文本对象
EMChatText *chatText = [[EMChatText alloc] initWithText:text];
//创建一个文本消息体
EMTextMessageBody *textBody = [[EMTextMessageBody alloc] initWithChatObject:chatText];
[self sendMessage:textBody];
}
#pragma mark 发送语音消息
-(void)sendVoice:(NSString *)recordPath duration:(NSInteger)duration{
// 1.构造一个语音消息体
EMChatVoice *chatVoice = [[EMChatVoice alloc] initWithFile:recordPath displayName:@"[语音]"];
// chatVoice.duration = duration;
EMVoiceMessageBody *voiceBody = [[EMVoiceMessageBody alloc] initWithChatObject:chatVoice];
voiceBody.duration = duration;
[self sendMessage:voiceBody];
}
在加入方法
- (IBAction)showImgPickerAction:(id)sender {
//显示图片选择的控制器
UIImagePickerController *imgPicker = [[UIImagePickerController alloc] init];
// 设置源
imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
imgPicker.delegate = self;
[self presentViewController:imgPicker animated:YES completion:NULL];
}
/**用户选中图片的回调*/
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
//1.获取用户选中的图片
UIImage *selectedImg = info[UIImagePickerControllerOriginalImage];
//2.发送图片
[self sendImg:selectedImg];
//3.隐藏当前图片选择控制器
[self dismissViewControllerAnimated:YES completion:NULL];
}
打开 Main.storyboard
找到 XMGChatViewController 控制器
连接 moreBtn按钮 的 showImgPickerAction 事件
运行
查看沙盒 Library/appdata/Chat/lamcoChat/messages下面是否有图片
05.显示时间的cell
打开 XMGAudioPlayTool.h
加入 +(void)stop;
打开 XMGAudioPlayTool.h
加入方法
+(void)stop{
//停止播放语音
[[EMCDDeviceManager sharedInstance] stopPlaying];
//移除动画
[animatingImageView stopAnimating];
[animatingImageView removeFromSuperview];
}
打开 XMGChatCell.m
加入
@interface XMGChatCell()
/** 聊天图片控件 */
@property (nonatomic,strong) UIImageView *chatImgView;
@end
在类中加入方法
-(UIImageView *)chatImgView{
if (!_chatImgView) {
_chatImgView = [[UIImageView alloc] init];
}
return _chatImgView;
}
在 setMessage 方法第一句位置加入
//重用时,把聊天图片控件移除
[self.chatImgView removeFromSuperview];
将 showImage方法中的
//1.cell里添加一个UIImageView
UIImageView *chatImgView = [[UIImageView alloc] init];
[self.messageLabel addSubview:chatImgView];
chatImgView.backgroundColor = [UIColor redColor];
改为
//1.cell里添加一个UIImageView
[self.messageLabel addSubview:self.chatImgView];
将 chatImgView.frame = thumbnailFrm;
改为 self.chatImgView.frame = thumbnailFrm;
将
if ([manager fileExistsAtPath:imgBody.thumbnailLocalPath]) {
#warning 本地路径使用fileURLWithPath方法
[chatImgView sd_setImageWithURL:[NSURL fileURLWithPath:imgBody.thumbnailLocalPath] placeholderImage:palceImg];
}else{
// 如果本地图片不存,从网络加载图片
[chatImgView sd_setImageWithURL:[NSURL URLWithString:imgBody.thumbnailRemotePath] placeholderImage:palceImg];
}
改为
if ([manager fileExistsAtPath:imgBody.thumbnailLocalPath]) {
#warning 本地路径使用fileURLWithPath方法
[self.chatImgView sd_setImageWithURL:[NSURL fileURLWithPath:imgBody.thumbnailLocalPath] placeholderImage:palceImg];
}else{
// 如果本地图片不存,从网络加载图片
[self.chatImgView sd_setImageWithURL:[NSURL URLWithString:imgBody.thumbnailRemotePath] placeholderImage:palceImg];
}
右键 Chat【聊天】 … new class name:XMGTimeCell
superclass:UITableViewCell
打开 XMGTimeCell.h
加入 @property (weak,nonatomic) IBOutlet UILabel *timeLabel;
说明:
时间显示的规则
同一分中内的消息,只显示一个时间
/*
15:52
msg1 15:52:10
msg2 15:52:08
msg2 15:52:02
*/
/*今天:时:分 (HH:mm)
*昨天:昨天 +时 +分 (昨天 HH:mm)
*昨天以前:(前天)年:月:日时分(2015-09-26 15:27)
*/
打开 XMGChatViewController.m
加入
#import "XMGAudioPlayTool.h"
#import "XMGTimeCell.h"
在 viewDidLoad 方法中的
[super viewDidLoad];
下面加入
//设置背景颜色
self.tableView.backgroundColor = [UIColor colorWithRed:246/255.0 green:246/255.0 blue:246/255.0 alpha:1];
在 loadLocalChatRecords 方法中的第一句位置加入
//假设在数组的第一位置添加时间
[self.dataSources addObject:@"16:06"];
在 heightForRowAtIndexPath
//时间cell的高度是固定
if ([self.dataSources[indexPath.row] isKindOfClass:[NSStringclass]]) {
return18;
}
在 cellForRowAtIndexPath 方法中的第一句位置加入
//判断数据源类型
if ([self.dataSources[indexPath.row] isKindOfClass:[NSStringclass]]) {//显示时间cell
XMGTimeCell *timeCell = [tableView dequeueReusableCellWithIdentifier:@"TimeCell"];
timeCell.timeLabel.text = self.dataSources[indexPath.row];
return timeCell;
}
在 voiceAction 方法中的
self.recordBtn.hidden = !self.recordBtn.hidden;下面加入
self.textView.hidden = !self.textView.hidden;
在加入方法
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
//停止语音播放
[XMGAudioPlayTool stop];
}
打开 Main.storyboard
找到 XMGChatViewController 控制器
清除掉 ReceiverCell、SenderCell 2个单元格的背景色 clear color
选中 table view Separator:None
在 Table View 中添加 cell class:XMGTimeCell
label:TimeCell
Identifier:TimeCell
background: Light Gray Color
row height:18
在 TimeCell 中添加 label font:13
设置约束 点击右下角第一个 勾选 Horizontallyin Container
勾选 Vertically in Container
连接 timeLabel输出口
运行
06.显示时间计算
右键 Chat【聊天】 —new class name:XMGTimeTool
superclass:NSObject
打开 XMGTimeTool.h
加入
//时间戳
+(NSString *)timeStr:(longlong)timestamp;
打开 XMGTimeTool.m
加入 #import <CoreGraphics/CoreGraphics.h>
在加入
+(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 = [calendar components: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 = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate: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];
}
打开 XMGChatViewController.m
加入 #import"XMGTimeTool.h"
在延展中加入
/** 当前添加的时间 */
@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 = [XMGTimeTool timeStr:msg.timestamp];
if (![self.currentTimeStr isEqualToString:timeStr]) {
[self.dataSources addObject:timeStr];
self.currentTimeStr = timeStr;
}
// 2.再加EMMessage
[self.dataSources addObject:msg];
}
修改 loadLocalChatRecords 方法如下
-(void)loadLocalChatRecords{
//假设在数组的第一位置添加时间
// [self.dataSources addObject:@"16:06"];
// 要获取本地聊天记录使用会话对象
EMConversation *conversation = [[EaseMob sharedInstance].chatManager conversationForChatter:self.buddy.username conversationType:eConversationTypeChat];
// 加载与当前聊天用户所有聊天记录
NSArray *messages = [conversation loadAllMessages];
// for (id obj in messages) {
// NSLog(@"%@",[obj class]);
// }
// 添加到数据源
// [self.dataSources addObjectsFromArray:messages];
for (EMMessage *msgObjin messages) {
[self addDataSourcesWithMessage:msgObj];
}
}
将 sendMessage 方法中的
[self.dataSources addObject:msgObj];
改为
[self addDataSourcesWithMessage:msgObj];
运行
07.显示历史会话记录
通过沙盒找到数据库文件 查看里面的表 Conversation 历史会话记录表
打开 XMGConversationViewController.m
在延展中加入
/** 历史会话记录 */
@property (nonatomic,strong) NSArray *conversations;
在 viewDidLoad 方法中的
//设置代理
[[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
下面加入
//获取历史会话记录
[self loadConversations];
在类中加入方法
-(void)loadConversations{
//获取历史会话记录
//1.从内存获取历史会话记录
NSArray *conversations = [[EaseMob sharedInstance].chatManager conversations];
//2.如果内存里没有会话记录,从数据库Conversation表
if (conversations.count ==0) {
conversations = [[EaseMob sharedInstance].chatManager loadAllConversationsFromDatabaseWithAppend2Chat:YES];
}
NSLog(@"zzzzzzz %@",conversations);
self.conversations = conversations;
//显示总的未读数
[self showTabBarBadge];
}
-(void)showTabBarBadge{
//遍历所有的会话记录,将未读取的消息数进行累
NSInteger totalUnreadCount = 0;
for (EMConversation *conversationin self.conversations) {
totalUnreadCount += [conversation unreadMessagesCount];
}
self.navigationController.tabBarItem.badgeValue = [NSString stringWithFormat:@"%ld",totalUnreadCount];
}
修改 numberOfSectionsInTableView、numberOfRowsInSection2个方法如下
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
#warning Incomplete method implementation.
// Return the number of rows in the section.
returnself.conversations.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *ID = @"ConversationCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
//获取会话模型
EMConversation *conversaion = self.conversations[indexPath.row];
// 显示数据
// 1.显示用户名
cell.textLabel.text = [NSString stringWithFormat:@"%@ ====未读消息数:%ld",conversaion.chatter,[conversaion unreadMessagesCount]];
// 2.显示最新的一条记录
// 获取消息体
id body = conversaion.latestMessage.messageBodies[0];
if ([body isKindOfClass:[EMTextMessageBodyclass]]) {
EMTextMessageBody *textBody = body;
cell.detailTextLabel.text = textBody.text;
}elseif ([body isKindOfClass:[EMVoiceMessageBodyclass]]){
EMVoiceMessageBody *voiceBody = body;
cell.detailTextLabel.text = [voiceBody displayName];
}elseif([body isKindOfClass:[EMImageMessageBodyclass]]){
EMImageMessageBody *imgBody = body;
cell.detailTextLabel.text = imgBody.displayName;
}else{
cell.detailTextLabel.text = @"未知消息类型";
}
return cell;
}
在加入方法
#pragma mark 接收好友的添加请求
-(void)didReceiveBuddyRequest:(NSString *)username message:(NSString *)message{
// 赋值
self.buddyUsername = username;
// 对话框
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"好友添加请求" message:message delegate:self cancelButtonTitle:@"拒绝" otherButtonTitles:@"同意",nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex ==0) {//拒绝好友请求
[[EaseMob sharedInstance].chatManager rejectBuddyRequest:self.buddyUsername reason:@"我不认识你" error:nil];
}else{//同意好友请求
[[EaseMob sharedInstance].chatManager acceptBuddyRequest:self.buddyUsername error:nil];
}
}
#pragma mark 监听被好友删除
-(void)didRemovedByBuddy:(NSString *)username{
NSString *message = [username stringByAppendingString:@"把你删除"];
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"xxxx" message:message delegate:nil cancelButtonTitle:@"知道了" otherButtonTitles:nil, nil];
[alert show];
}
在加入方法
#pragma mark 历史会话列表更新
-(void)didUpdateConversationList:(NSArray *)conversationList{
//给数据源重新赋值
self.conversations = conversationList;
//刷新表格
[self.tableView reloadData];
//显示总的未读数
[self showTabBarBadge];
}
#pragma mark 未读消息数改变
- (void)didUnreadMessagesCountChanged{
//更新表格
[self.tableView reloadData];
//显示总的未读数
[self showTabBarBadge];
}
打开 XMGChatViewController.m
将 didReceiveMessage 方法中的
[self.dataSources addObject:message];
改为
[self addDataSourcesWithMessage:message];
打开 Main.storyboard
找到 XMGConversationViewController 控制器
选中 UITableView Cell Identifier:ConversationCell
运行
08.设置消息为已读
历史的会话记录(EMConverstion)
EMConverstion
1.从数据库获取聊天记录
2.设置消息为已读
打开 XMGConversationViewController.m
加入 #import"XMGChatViewController.h"
在类中加入方法
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//进入到聊天控制器
//1.从storybaord加载聊天控制器
XMGChatViewController *chatVc = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"ChatPage"];
//会话
EMConversation *conversation = self.conversations[indexPath.row];
EMBuddy *buddy = [EMBuddy buddyWithUsername:conversation.chatter];
//2.设置好友属性
chatVc.buddy = buddy;
//3.展现聊天界面
[self.navigationController pushViewController:chatVc animated:YES];
}
打开 XMGChatViewController.m
在延展中加入
/** 当前会话对象 */
@property (nonatomic,strong) EMConversation *conversation;
在 loadLocalChatRecords 方法中的
// 要获取本地聊天记录使用会话对象
EMConversation *conversation = [[EaseMob sharedInstance].chatManager conversationForChatter:self.buddy.username conversationType:eConversationTypeChat];
下面加入
self.conversation = conversation;
在 addDataSourcesWithMessage 方法中的
[self.dataSources addObject:msg];
下面加入
// 3.设置消息为已读取
[self.conversation markMessageWithId:msg.messageId asRead:YES];
打开 Main.storyboard
找到 XMGChatViewController 控制器
设置 Storyboard ID:ChatPage
运行
09.面试题
(1)是否使用过XMPP,XMPP的实现原理 参看PPT-27到31
XMPP是一个即时通讯的协议,它规范了用于即时通信在网络上数据传输格式的,比如登录,获取好友列表等等的格式。XMPP在网络传输的数据是XML格式
比如登录:把用户名和密码放在xml的标签中,传输到服务器
XMPP是一个基于个Socket通过的网络协议,目的是为了保存长连接,以实现即时通讯功能
XMPP的客户端是使用一个XMPPFramework框架实现
XMPP的服务器是使用Openfire,一个开源的服务器
客户端获取到服务器发送过来的好友消息,客户端需要对XML进行解析,使用的解析框架的KissXML框架,而不是NSXMLParser/GDataXML
(2)在使用XMPP的时候有没有需要什么困难
发送附件(图片,语音,文档…)时比较麻烦
XMPP框架没有提供发送附件的功能,需要自己实现
实现方法,把文件上传到文件服务器,上传成功后获取文件保存路径,再把附件的路径发送给好友
(3是否使用过环信,简单的说下环信的实现原理
环信是一个即时通信的服务提供商
环信使用的是XMPP协议,它是再XMPP的基础上进行二次开发,对服务器Openfire和客户端进行功能模型的添加和客户端SDK的封装,环信的本质还是使用XMPP,基本于Socket的网络通信
环信内部实现了数据缓存,会把聊天记录添加到数据库,把附件(如音频文件,图片文件)下载到本地,使程序员更多时间是花到用户即时体验上
环信内部已经实现了视频,音频,图片,其它附件发送功能
环信使用公司可以节约时间成本
不需要公司内部搭建服务器
客户端的开发,使用环信SDK比使用XMPPFramework更简洁方便