04环信聊天界面 - 显示时间的cell

1.拖一个UITableViewCell,再向里面添加一个UILabel,设置label垂直居中对齐,水平居中对齐。自定义一个继承与UITabelViewCell的TimeCell类。


2.聊天控制器tableView的dataSoures之前是存放的 EMMessage 消息模型,现在需要加入 时间(字符串)。

我们采用的方案是单独写一个方法来添加数据源,在此方法里,判断是否要给数据源加入 时间数据

/**
 *  加入数据源的方法
 */
- (void)addDataSourcesWithMessage:(EMMessage *)message
{
    // 1.判断EMMessage对象前面是否要加『时间』
    NSString *timeStr = [TimeTool timeStr:message.timestamp];
    if (![self.currentTimeStr isEqualToString:timeStr]) {
        [self.dataSources addObject:timeStr];
        self.currentTimeStr = timeStr;
    }
   
    // 2.再加EMMessage
    [self.dataSources addObject:message];
}
3.用到的处理时间格式的工具类
//
//  TimeTool.h
#import <Foundation/Foundation.h>

@interface TimeTool : NSObject
/**
 *  返回格式化后的时间
 *
 *  @param timestamp 时间戳
 */
+ (NSString *)timeStr:(long long)timestamp;
@end
//
//  TimeTool.m


#import "TimeTool.h"
#import <UIKit/UIKit.h>

@implementation TimeTool

+ (NSString *)timeStr:(long long)timestamp
{
    NSCalendar *calendar = [NSCalendar currentCalendar];
    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;
    
    // 获取消息发送时间的年、月、日
    NSDate *msgDate = [NSDate dateWithTimeIntervalSince1970:timestamp/1000.0];
    components = [calendar components:NSCalendarUnitYear| NSCalendarUnitMonth|NSCalendarUnitDay fromDate:msgDate];
    CGFloat msgYear = components.year;
    CGFloat msgMonth = components.month;
    CGFloat msgDay = components.day;
    
    // 判断
    NSDateFormatter *dateFmt = [[NSDateFormatter alloc] init];
    if (currentYear == msgYear && currentMonth == msgMonth && currentDay == msgDay) {
        //今天
        dateFmt.dateFormat = @"HH:mm";
    }else if (currentYear == msgYear && currentMonth == msgMonth && currentDay-1 == msgDay ){
        //昨天
         dateFmt.dateFormat = @"昨天 HH:mm";
    }else{
        //昨天以前
         dateFmt.dateFormat = @"yyyy-MM-dd HH:mm";
    }
    
    return [dateFmt stringFromDate:msgDate];
}

@end
4.最后聊天控制器的代码如下:
//
//  ChatViewController.m

#import "ChatViewController.h"
#import "ChatCell.h"
#import "EaseMob.h"
#import "EMCDDeviceManager.h"
#import "AudioPlayTool.h"
#import "TimeCell.h"
#import "TimeTool.h"

@interface ChatViewController ()<UITableViewDataSource,UITableViewDelegate,UITextViewDelegate,EMChatManagerDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
/**
 *  inputToolbar(输入工具栏)底部的约束
 */
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputToolbarBottomConstraint;
/**
 *  inputToolbar高度的约束
 */
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *inputToolbarHeightConstraint;
@property (weak, nonatomic) IBOutlet UIButton *recordBtn;
@property (weak, nonatomic) IBOutlet UIButton *voiceBtn;
@property (weak, nonatomic) IBOutlet UITextView *textView;

/**
 *  里边存的都是消息模型
 */
@property(nonatomic,strong)NSMutableArray *dataSources;
@property (weak, nonatomic) IBOutlet UITableView *tableView;

/**
 *  专门用来计算高度的cell工具对象
 */
@property(nonatomic,strong) ChatCell *chatCellTool;

/**
 *  当前添加的时间字符串
 */
@property(nonatomic,copy)NSString *currentTimeStr;
@end

@implementation ChatViewController

- (NSMutableArray *)dataSources
{
    if (!_dataSources) {
        _dataSources = [NSMutableArray array];
    }
    return _dataSources;
}

/**
 *  懒加载创建chatCellTool
 */
- (ChatCell *)chatCellTool
{
    if (!_chatCellTool) {
        //Identifier 用recivierCell或senderCell都可以,因为2个cell其实内部
        //Identifier 声明在cell内部的
        _chatCellTool = [self.tableView dequeueReusableCellWithIdentifier:senderCell];
    }
    return _chatCellTool;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 0.设置标题
    self.title = self.buddy.username;
   
    // 1.监听键盘的弹出
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    // 2.监听键盘的退出
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];

    // 3.加载本地数据库的聊天记录
    [self loadLocalChatRecords];
    
    
    // 4.设置聊天管理器的代理
    [[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
    
    // 5.tableView滚动到最底部
    [self scrollToBottom];
}

/**
 *  加载本地的聊天记录
 */
- (void)loadLocalChatRecords
{
    // 获取本地聊天记录 使用回话对象
   EMConversation *conversation = [[EaseMob sharedInstance].chatManager conversationForChatter:self.buddy.username conversationType:eConversationTypeChat];
    
    // 加载与当前聊天用户的所有聊记录
    NSArray *messages = [conversation loadAllMessages];
    
    // 添加到数据源
//    [self.dataSources addObjectsFromArray:messages];
    for (EMMessage *msg in messages) {
        [self addDataSourcesWithMessage:msg];
    }
}

/**
 *  键盘退出会调用
 */
- (void)keyboardWillHide:(NSNotification *)notice
{
    // inputToolbar回到原位
    self.inputToolbarBottomConstraint.constant = 0;
    [UIView animateWithDuration:0.25 animations:^{
        [self.view layoutIfNeeded];
    }];
}

/**
 *  键盘弹出会调用
 */
- (void)keyboardWillShow:(NSNotification *)notice
{
    // 1.获取键盘高度
    CGFloat heigth = [notice.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
    
    // 2.更改inputToolbar底部约束
    self.inputToolbarBottomConstraint.constant = heigth;
    [UIView animateWithDuration:0.25 animations:^{
        [self.view layoutIfNeeded]; 
    }];
    
    // 3.滚动tableView
    [self scrollToBottom];
}

/**
 *  添加通知要移除,我们可以在这里方法里移除
 */
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark -UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.dataSources.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //判断数据源类型
    if ([self.dataSources[indexPath.row] isKindOfClass:[NSString class]]) {
        TimeCell *cell = [tableView dequeueReusableCellWithIdentifier:@"timeCell"];
        cell.timeLabel.text = self.dataSources[indexPath.row];
        return cell;
    }
    
    // 1.获取消息模型
    EMMessage *message = self.dataSources[indexPath.row];

    // 2.创建cell
    ChatCell *cell = nil;
    if ([message.from isEqualToString:self.buddy.username]) { //好友
        cell = [tableView dequeueReusableCellWithIdentifier:recivierCell];
    }else{ //自己
        cell = [tableView dequeueReusableCellWithIdentifier:senderCell];
    }
    
    // 3.给cell传递数据模型
    cell.message = message;
    
    // 4.返回
    return cell;
}

#pragma mark - UITableViewDelegate
/**
 *  返回cell的高度
 */
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 时间cell的高度是固定的
    if ([self.dataSources[indexPath.row] isKindOfClass:[NSString class]]) {
        return 20;
    }
    
    // 1.获取消息模型传递给cell,让cell有了数据然后调整布局
    EMMessage *msg = self.dataSources[indexPath.row];
    self.chatCellTool.message = msg;
    
    // 2.必须上面cell有了数据调整完布局,才能得到高度
    return [self.chatCellTool cellHeight];
}

#pragma mark - UITableViewDelegate
/**
 *  开始滑动scrollView会调用
 */
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
    // 停止语音播放
    [AudioPlayTool stop];
}

#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
    // 1.计算textView的高度,调整整个inputToolbar的高度
    CGFloat textViewH = 0;
    CGFloat minH = 33; //textView最小高度
    CGFloat maxH = 68; //textView最大高度
    CGFloat contentH = textView.contentSize.height;
    if (contentH < minH) {
        textViewH = minH;
    }else if (contentH > maxH){
        textViewH = maxH;
    }else{
        textViewH = contentH;
    }
    
    
    // 2.监听用户是否点击了键盘的"Send"按钮 -- 判断最后一个字符是否是换行符
    if ([textView.text hasSuffix:@"\n"]) {
        // 发送消息
        [self sendText:textView.text];
        
        // 清空textView中的文字
        textView.text = nil;
        
        // 用户点击"发送"之后,textView高度还原
        textViewH = minH;
    }
    
    // 3.调整整个inputToolbar的高度
    /**
     7为textView到顶部的距离
     6为textView到底部的距离
     */
    self.inputToolbarHeightConstraint.constant = 7 + 6 + textViewH;
    [UIView animateWithDuration:0.25 animations:^{
        [self.view layoutIfNeeded];
    }];
    
    
    // 4.让光标回到原位
    [textView setContentOffset:CGPointZero animated:YES];
    [textView scrollRangeToVisible:textView.selectedRange];
}

#pragma mark - 发送消息(文本、语音、图片)
/**
 *  发送消息
 *
 *  @param body 消息体
 */
- (void)sendMessage:(id<IEMMessageBody>)body
{
    // 1.构造消息对象
    EMMessage *msg = [[EMMessage alloc] initWithReceiver:self.buddy.username bodies:@[body]];
    msg.messageType = eMessageTypeChat;
    
    // 2.发送消息
    [[EaseMob sharedInstance].chatManager asyncSendMessage:msg progress:nil prepare:^(EMMessage *message, EMError *error) {
        // 准备发送
    } onQueue:nil completion:^(EMMessage *message, EMError *error) {
        // 发送成功
    } onQueue:nil];
    
    // 4.把消息添加到数据源
    [self addDataSourcesWithMessage:msg];
    
    // 5.刷新表格
    [self.tableView reloadData];
    
    // 6.滚动tableView
    [self scrollToBottom];

}

/**
 *  发送文本消息
 */
- (void)sendText:(NSString *)text
{
    // 0.处理字符串中的换行符
    text = [text substringToIndex:text.length -1];
    
    // 1.准备创建消息实例需要的参数
    EMChatText *chatText = [[EMChatText alloc] initWithText:text];
    EMTextMessageBody *textBody = [[EMTextMessageBody alloc] initWithChatObject:chatText];
    
    // 2.发送
    [self sendMessage:textBody];
}

/**
 *  发送语音消息
 *
 *  @param recordPath 语音文件路径
 *  @param duration   时间
 */
- (void)sendVoice:(NSString *)recordPath duration:(NSInteger)duration
{
    // 1.构造一个 语音消息体
    EMChatVoice *chatVoice = [[EMChatVoice alloc] initWithFile:recordPath displayName:@"[语音]"];
    EMVoiceMessageBody *voiceBody = [[EMVoiceMessageBody alloc] initWithChatObject:chatVoice];
    voiceBody.duration = duration;
    
    // 2.发送
    [self sendMessage:voiceBody];
}

/**
 *  发送图片消息
 */
- (void)sendImage:(UIImage *)selectedImage
{
    // 1.构造图片消息体
    EMChatImage *orginalChatImg = [[EMChatImage alloc] initWithUIImage:selectedImage displayName:@"[图片]"];
    EMImageMessageBody *imgBody = [[EMImageMessageBody alloc]initWithImage:orginalChatImg thumbnailImage:nil];
    
    // 2.发送
    [self sendMessage:imgBody];
}

/**
 *  tableView滚动到最后一行
 */
- (void)scrollToBottom
{
    if (self.dataSources.count == 0) return;
    
    NSIndexPath *lastIndex = [NSIndexPath indexPathForRow:self.dataSources.count-1 inSection:0];
    [self.tableView scrollToRowAtIndexPath:lastIndex atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}

#pragma mark - EMChatManagerDelegate
/**
 *  在线普通消息会走以下回调
 */
- (void)didReceiveMessage:(EMMessage *)message
{
    if ([message.from isEqualToString:self.buddy.username]) {
        [self addDataSourcesWithMessage:message];
        [self.tableView reloadData];
        [self scrollToBottom];
    }
}

#pragma mark - action
/**
 *  点击上传图片
 */
- (IBAction)showImgPickerAction:(UIButton *)sender
{
    // 图片选择控制器
    UIImagePickerController *imgPicker = [[UIImagePickerController alloc] init];
    imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    imgPicker.delegate = self;
    [self presentViewController:imgPicker animated:YES completion:nil];
}

#pragma mark - UIImagePickerControllerDelegate
/**
 *  用户选中图片的回调
 */
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    // 获取选中的图片
    UIImage *selectedImg = info[UIImagePickerControllerOriginalImage];
    
    // 发送图片消息
    [self sendImage:selectedImg];
    
    // 退出当前图片选择的控制器
    [self dismissViewControllerAnimated:YES completion:nil];
}

/**
 *  VoiceBtn被点击了
 */
- (IBAction)clickedVoiceBtn:(UIButton *)sender
{
    // 按钮图片切换
    self.voiceBtn.selected = !self.voiceBtn.selected;
    self.textView.hidden = !self.textView.hidden;
    
    // 1.显示录音按钮
    self.recordBtn.hidden = !self.recordBtn.hidden;
    if (self.recordBtn.hidden == NO) { //录音按钮要显示
        // inputToolbar的高度要回到默认高度
        self.inputToolbarHeightConstraint.constant = 46;
        // 退出键盘
        [self.view endEditing:YES];
    }else{
        // 当不录音的时候,键盘显示
        [self.textView becomeFirstResponder];
        // 恢复inputToolbar的高度
        [self textViewDidChange:self.textView];
    }
}

/**
 *  手指按下去开始录音
 */
- (IBAction)touchDownRecordBtn:(id)sender
{
    // 文件名以时间命令
    int x = arc4random() % 100000;
    NSTimeInterval time = [[NSDate date] timeIntervalSince1970];
    NSString *fileName = [NSString stringWithFormat:@"%d%d",(int)time,x];
    
    [[EMCDDeviceManager sharedInstance] asyncStartRecordingWithFileName:fileName completion:^(NSError *error) {
        if (!error) {
            //开始录音成功
        }
    }];
}

/**
 *  手指松开结束录音
 */
- (IBAction)touchUpInsideRecordBtn:(id)sender
{
    [[EMCDDeviceManager sharedInstance] asyncStopRecordingWithCompletion:^(NSString *recordPath, NSInteger aDuration, NSError *error) {
        if (!error) {
            //录音完成
            //发送语音
            [self sendVoice:recordPath duration:aDuration];
        }
    }];
}

/**
 *  手指从按钮的外面松开结束录音

 */
- (IBAction)touchUpOutsideRecordBtn:(id)sender
{
    
}

/**
 *  加入数据源的方法
 */
- (void)addDataSourcesWithMessage:(EMMessage *)message
{
    // 1.判断EMMessage对象前面是否要加『时间』
    NSString *timeStr = [TimeTool timeStr:message.timestamp];
    if (![self.currentTimeStr isEqualToString:timeStr]) {
        [self.dataSources addObject:timeStr];
        self.currentTimeStr = timeStr;
    }
   
    // 2.再加EMMessage
    [self.dataSources addObject:message];
}

@end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值