chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:35:01 +08:00
commit a2e318a963
2974 changed files with 158180 additions and 0 deletions
@@ -0,0 +1,14 @@
//
// TLExpressionMessageCell.h
// TLChat
//
// Created by 李伯坤 on 16/3/19.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLMessageBaseCell.h"
#import "TLExpressionMessage.h"
@interface TLExpressionMessageCell : TLMessageBaseCell
@end
@@ -0,0 +1,124 @@
//
// TLExpressionMessageCell.m
// TLChat
//
// Created by 李伯坤 on 16/3/19.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLExpressionMessageCell.h"
#import <SDWebImage/UIImage+GIF.h>
#import "NSFileManager+TLChat.h"
@interface TLExpressionMessageCell ()
@property (nonatomic, strong) UIImageView *msgImageView;
@end
@implementation TLExpressionMessageCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self.contentView addSubview:self.msgImageView];
}
return self;
}
- (void)setMessage:(TLExpressionMessage *)message
{
[self.msgImageView setAlpha:1.0]; // 取消长按效果
// if (self.message && [self.message.messageID isEqualToString:message.messageID]) {
// return;
// }
TLMessageOwnerType lastOwnType = self.message ? self.message.ownerTyper : -1;
[super setMessage:message];
NSData *data = [NSData dataWithContentsOfFile:message.path];
if (data) {
[self.msgImageView setImage:[UIImage imageNamed:message.path]];
[self.msgImageView setImage:[UIImage sd_animatedGIFWithData:data]];
}
else { // 表情组被删掉,先从缓存目录中查找,没有的话在下载并存入缓存目录
NSString *cachePath = [NSFileManager cacheForFile:[NSString stringWithFormat:@"%@_%@.gif", message.emoji.gid, message.emoji.eId]];
NSData *data = [NSData dataWithContentsOfFile:cachePath];
if (data) {
[self.msgImageView setImage:[UIImage imageNamed:cachePath]];
[self.msgImageView setImage:[UIImage sd_animatedGIFWithData:data]];
}
else {
__weak typeof(self) weakSelf = self;
[self.msgImageView tt_setImageWithURL:TLURL(message.url) completed:^(UIImage *image, NSError *error, TLImageCacheType cacheType, NSURL *imageURL) {
if ([[imageURL description] isEqualToString:[(TLExpressionMessage *)weakSelf.message url]]) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:imageURL];
[data writeToFile:cachePath atomically:NO]; // 再写入到缓存中
if ([[imageURL description] isEqualToString:[(TLExpressionMessage *)weakSelf.message url]]) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.msgImageView setImage:[UIImage sd_animatedGIFWithData:data]];
});
}
});
}
}];
}
}
if (lastOwnType != message.ownerTyper) {
if (message.ownerTyper == TLMessageOwnerTypeSelf) {
[self.msgImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.usernameLabel.mas_bottom).mas_offset(5);
make.right.mas_equalTo(self.messageBackgroundView).mas_offset(-10);
}];
}
else if (message.ownerTyper == TLMessageOwnerTypeFriend){
[self.msgImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.usernameLabel.mas_bottom).mas_offset(5);
make.left.mas_equalTo(self.messageBackgroundView).mas_offset(10);
}];
}
}
[self.msgImageView mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(message.messageFrame.contentSize);
}];
}
#pragma mark - Event Response -
- (void)longPressMsgBGView:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateBegan) {
return;
}
[self.msgImageView setAlpha:0.7]; // 比较low的选中效果
if (self.delegate && [self.delegate respondsToSelector:@selector(messageCellLongPress:rect:)]) {
CGRect rect = self.msgImageView.frame;
[self.delegate messageCellLongPress:self.message rect:rect];
}
}
- (void)doubleTabpMsgBGView
{
if (self.delegate && [self.delegate respondsToSelector:@selector(messageCellDoubleClick:)]) {
[self.delegate messageCellDoubleClick:self.message];
}
}
#pragma mark - Getter -
- (UIImageView *)msgImageView
{
if (_msgImageView == nil) {
_msgImageView = [[UIImageView alloc] init];
[_msgImageView setUserInteractionEnabled:YES];
UILongPressGestureRecognizer *longPressGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressMsgBGView:)];
[_msgImageView addGestureRecognizer:longPressGR];
UITapGestureRecognizer *doubleTapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTabpMsgBGView)];
[doubleTapGR setNumberOfTapsRequired:2];
[_msgImageView addGestureRecognizer:doubleTapGR];
}
return _msgImageView;
}
@end
@@ -0,0 +1,14 @@
//
// TLImageMessageCell.h
// TLChat
//
// Created by 李伯坤 on 16/3/14.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLMessageBaseCell.h"
#import "TLImageMessage.h"
@interface TLImageMessageCell : TLMessageBaseCell
@end
@@ -0,0 +1,118 @@
//
// TLImageMessageCell.m
// TLChat
//
// Created by 李伯坤 on 16/3/14.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLImageMessageCell.h"
#import "TLMessageImageView.h"
#import "NSFileManager+TLChat.h"
#define MSG_SPACE_TOP 2
@interface TLImageMessageCell ()
@property (nonatomic, strong) TLMessageImageView *msgImageView;
@end
@implementation TLImageMessageCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self.contentView addSubview:self.msgImageView];
}
return self;
}
- (void)setMessage:(TLImageMessage *)message
{
[self.msgImageView setAlpha:1.0]; // 取消长按效果
if (self.message && [self.message.messageID isEqualToString:message.messageID]) {
return;
}
TLMessageOwnerType lastOwnType = self.message ? self.message.ownerTyper : -1;
[super setMessage:message];
if ([message imagePath]) {
NSString *imagePath = [NSFileManager pathUserChatImage:[message imagePath]];
[self.msgImageView setThumbnailPath:imagePath highDefinitionImageURL:[message imagePath]];
}
else {
[self.msgImageView setThumbnailPath:nil highDefinitionImageURL:[message imagePath]];
[self.msgImageView tt_setImageWithURL:[NSURL URLWithString:[message imageURL]]];
}
if (lastOwnType != message.ownerTyper) {
if (message.ownerTyper == TLMessageOwnerTypeSelf) {
[self.msgImageView setBackgroundImage:[UIImage imageNamed:@"message_sender_bg"]];
[self.msgImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.messageBackgroundView);
make.right.mas_equalTo(self.messageBackgroundView);
}];
}
else if (message.ownerTyper == TLMessageOwnerTypeFriend){
[self.msgImageView setBackgroundImage:[UIImage imageNamed:@"message_receiver_bg"]];
[self.msgImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.messageBackgroundView);
make.left.mas_equalTo(self.messageBackgroundView);
}];
}
}
[self.msgImageView mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(message.messageFrame.contentSize);
}];
}
#pragma mark - Event Response -
- (void)tapMessageView
{
if (self.delegate && [self.delegate respondsToSelector:@selector(messageCellTap:)]) {
[self.delegate messageCellTap:self.message];
}
}
- (void)longPressMsgBGView:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateBegan) {
return;
}
[self.msgImageView setAlpha:0.7]; // 比较low的选中效果
if (self.delegate && [self.delegate respondsToSelector:@selector(messageCellLongPress:rect:)]) {
CGRect rect = self.msgImageView.frame;
rect.size.height -= 10; // 北京图片底部空白区域
[self.delegate messageCellLongPress:self.message rect:rect];
}
}
- (void)doubleTabpMsgBGView
{
if (self.delegate && [self.delegate respondsToSelector:@selector(messageCellDoubleClick:)]) {
[self.delegate messageCellDoubleClick:self.message];
}
}
#pragma mark - Getter -
- (TLMessageImageView *)msgImageView
{
if (_msgImageView == nil) {
_msgImageView = [[TLMessageImageView alloc] init];
[_msgImageView setUserInteractionEnabled:YES];
UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapMessageView)];
[_msgImageView addGestureRecognizer:tapGR];
UILongPressGestureRecognizer *longPressGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressMsgBGView:)];
[_msgImageView addGestureRecognizer:longPressGR];
UITapGestureRecognizer *doubleTapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTabpMsgBGView)];
[doubleTapGR setNumberOfTapsRequired:2];
[_msgImageView addGestureRecognizer:doubleTapGR];
}
return _msgImageView;
}
@end
@@ -0,0 +1,32 @@
//
// TLMessageBaseCell.h
// TLChat
//
// Created by 李伯坤 on 16/3/1.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TLMessageCellDelegate.h"
#import "TLMessage.h"
@interface TLMessageBaseCell : UITableViewCell
@property (nonatomic, assign) id<TLMessageCellDelegate>delegate;
@property (nonatomic, strong) UILabel *timeLabel;
@property (nonatomic, strong) UIButton *avatarButton;
@property (nonatomic, strong) UILabel *usernameLabel;
@property (nonatomic, strong) UIImageView *messageBackgroundView;
@property (nonatomic, strong) TLMessage *message;
/**
* 更新消息,如果子类不重写,默认调用setMessage方法
*/
- (void)updateMessage:(TLMessage *)message;
@end
@@ -0,0 +1,221 @@
//
// TLMessageBaseCell.m
// TLChat
//
// Created by 李伯坤 on 16/3/1.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLMessageBaseCell.h"
#import "NSDate+TLChat.h"
#import "NSFileManager+TLChat.h"
#define TIMELABEL_HEIGHT 20.0f
#define TIMELABEL_SPACE_Y 10.0f
#define NAMELABEL_HEIGHT 14.0f
#define NAMELABEL_SPACE_X 12.0f
#define NAMELABEL_SPACE_Y 1.0f
#define AVATAR_WIDTH 40.0f
#define AVATAR_SPACE_X 8.0f
#define AVATAR_SPACE_Y 12.0f
#define MSGBG_SPACE_X 5.0f
#define MSGBG_SPACE_Y 1.0f
@interface TLMessageBaseCell ()
@end
@implementation TLMessageBaseCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self setBackgroundColor:[UIColor clearColor]];
[self setSelectionStyle:UITableViewCellSelectionStyleNone];
[self.contentView addSubview:self.timeLabel];
[self.contentView addSubview:self.avatarButton];
[self.contentView addSubview:self.usernameLabel];
[self.contentView addSubview:self.messageBackgroundView];
[self p_addMasonry];
}
return self;
}
- (void)setMessage:(TLMessage *)message
{
if (_message && [_message.messageID isEqualToString:message.messageID]) {
return;
}
[self.timeLabel setText:[NSString stringWithFormat:@" %@ ", message.date.chatTimeInfo]];
[self.usernameLabel setText:[message.fromUser chat_username]];
if ([message.fromUser chat_avatarPath].length > 0) {
NSString *path = [NSFileManager pathUserAvatar:[message.fromUser chat_avatarPath]];
[self.avatarButton setImage:[UIImage imageNamed:path] forState:UIControlStateNormal];
}
else {
[self.avatarButton tt_setImageWithURL:TLURL([message.fromUser chat_avatarURL]) forState:UIControlStateNormal];
}
// 时间
if (!_message || _message.showTime != message.showTime) {
[self.timeLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(message.showTime ? TIMELABEL_HEIGHT : 0);
make.top.mas_equalTo(self.contentView).mas_offset(message.showTime ? TIMELABEL_SPACE_Y : 0);
}];
}
if (!message || _message.ownerTyper != message.ownerTyper) {
// 头像
[self.avatarButton mas_remakeConstraints:^(MASConstraintMaker *make) {
make.width.and.height.mas_equalTo(AVATAR_WIDTH);
make.top.mas_equalTo(self.timeLabel.mas_bottom).mas_offset(AVATAR_SPACE_Y);
if(message.ownerTyper == TLMessageOwnerTypeSelf) {
make.right.mas_equalTo(self.contentView).mas_offset(-AVATAR_SPACE_X);
}
else {
make.left.mas_equalTo(self.contentView).mas_offset(AVATAR_SPACE_X);
}
}];
// 用户名
[self.usernameLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.avatarButton).mas_equalTo(-NAMELABEL_SPACE_Y);
if (message.ownerTyper == TLMessageOwnerTypeSelf) {
make.right.mas_equalTo(self.avatarButton.mas_left).mas_offset(- NAMELABEL_SPACE_X);
}
else {
make.left.mas_equalTo(self.avatarButton.mas_right).mas_equalTo(NAMELABEL_SPACE_X);
}
}];
// 背景
[self.messageBackgroundView mas_remakeConstraints:^(MASConstraintMaker *make) {
message.ownerTyper == TLMessageOwnerTypeSelf ? make.right.mas_equalTo(self.avatarButton.mas_left).mas_offset(-MSGBG_SPACE_X) : make.left.mas_equalTo(self.avatarButton.mas_right).mas_offset(MSGBG_SPACE_X);
make.top.mas_equalTo(self.usernameLabel.mas_bottom).mas_offset(message.showName ? 0 : -MSGBG_SPACE_Y);
}];
}
[self.usernameLabel setHidden:!message.showName];
[self.usernameLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(message.showName ? NAMELABEL_HEIGHT : 0);
}];
_message = message;
}
- (void)updateMessage:(TLMessage *)message
{
[self setMessage:message];
}
#pragma mark - Private Methods -
- (void)p_addMasonry
{
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.contentView).mas_offset(TIMELABEL_SPACE_Y);
make.centerX.mas_equalTo(self.contentView);
}];
[self.usernameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.avatarButton).mas_equalTo(-NAMELABEL_SPACE_Y);
make.right.mas_equalTo(self.avatarButton.mas_left).mas_offset(- NAMELABEL_SPACE_X);
}];
// Default - self
[self.avatarButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.contentView).mas_offset(-AVATAR_SPACE_X);
make.width.and.height.mas_equalTo(AVATAR_WIDTH);
make.top.mas_equalTo(self.timeLabel.mas_bottom).mas_offset(AVATAR_SPACE_Y);
}];
[self.messageBackgroundView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.avatarButton.mas_left).mas_offset(-MSGBG_SPACE_X);
make.top.mas_equalTo(self.usernameLabel.mas_bottom).mas_offset(-MSGBG_SPACE_Y);
}];
}
#pragma mark - Event Response -
- (void)avatarButtonDown:(UIButton *)sender
{
if (_delegate && [_delegate respondsToSelector:@selector(messageCellDidClickAvatarForUser:)]) {
[_delegate messageCellDidClickAvatarForUser:self.message.fromUser];
}
}
- (void)longPressMsgBGView:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateBegan) {
return;
}
[self.messageBackgroundView setHighlighted:YES];
if (_delegate && [_delegate respondsToSelector:@selector(messageCellLongPress:rect:)]) {
CGRect rect = self.messageBackgroundView.frame;
rect.size.height -= 10; // 北京图片底部空白区域
[_delegate messageCellLongPress:self.message rect:rect];
}
}
- (void)doubleTabpMsgBGView
{
if (_delegate && [_delegate respondsToSelector:@selector(messageCellDoubleClick:)]) {
[_delegate messageCellDoubleClick:self.message];
}
}
#pragma mark - Getter -
- (UILabel *)timeLabel
{
if (_timeLabel == nil) {
_timeLabel = [[UILabel alloc] init];
[_timeLabel setFont:[UIFont systemFontOfSize:12.0f]];
[_timeLabel setTextColor:[UIColor whiteColor]];
[_timeLabel setBackgroundColor:[UIColor grayColor]];
[_timeLabel setAlpha:0.7f];
[_timeLabel.layer setMasksToBounds:YES];
[_timeLabel.layer setCornerRadius:5.0f];
}
return _timeLabel;
}
- (UIButton *)avatarButton
{
if (_avatarButton == nil) {
_avatarButton = [[UIButton alloc] init];
[_avatarButton.layer setMasksToBounds:YES];
[_avatarButton.layer setBorderWidth:BORDER_WIDTH_1PX];
[_avatarButton.layer setBorderColor:[UIColor colorWithWhite:0.7 alpha:1.0].CGColor];
[_avatarButton addTarget:self action:@selector(avatarButtonDown:) forControlEvents:UIControlEventTouchUpInside];
}
return _avatarButton;
}
- (UILabel *)usernameLabel
{
if (_usernameLabel == nil) {
_usernameLabel = [[UILabel alloc] init];
[_usernameLabel setTextColor:[UIColor grayColor]];
[_usernameLabel setFont:[UIFont systemFontOfSize:12.0f]];
}
return _usernameLabel;
}
- (UIImageView *)messageBackgroundView
{
if (_messageBackgroundView == nil) {
_messageBackgroundView = [[UIImageView alloc] init];
[_messageBackgroundView setUserInteractionEnabled:YES];
UILongPressGestureRecognizer *longPressGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressMsgBGView:)];
[_messageBackgroundView addGestureRecognizer:longPressGR];
UITapGestureRecognizer *doubleTapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTabpMsgBGView)];
[doubleTapGR setNumberOfTapsRequired:2];
[_messageBackgroundView addGestureRecognizer:doubleTapGR];
}
return _messageBackgroundView;
}
@end
@@ -0,0 +1,24 @@
//
// TLMessageCellDelegate.h
// TLChat
//
// Created by 李伯坤 on 16/3/2.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol TLChatUserProtocol;
@class TLMessage;
@protocol TLMessageCellDelegate <NSObject>
- (void)messageCellDidClickAvatarForUser:(id<TLChatUserProtocol>)user;
- (void)messageCellTap:(TLMessage *)message;
- (void)messageCellLongPress:(TLMessage *)message rect:(CGRect)rect;
- (void)messageCellDoubleClick:(TLMessage *)message;
@end
@@ -0,0 +1,23 @@
//
// TLMessageImageView.h
// TLChat
//
// Created by 李伯坤 on 16/3/15.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TLMessageImageView : UIImageView
@property (nonatomic, strong) UIImage *backgroundImage;
/**
* 设置消息图片(规则:收到消息时,先下载缩略图到本地,再添加到列表显示,并自动下载大图)
*
* @param imagePath 缩略图Path
* @param imageURL 高清图URL
*/
- (void)setThumbnailPath:(NSString *)imagePath highDefinitionImageURL:(NSString *)imageURL;
@end
@@ -0,0 +1,66 @@
//
// TLMessageImageView.m
// TLChat
//
// Created by 李伯坤 on 16/3/15.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLMessageImageView.h"
@interface TLMessageImageView ()
@property (nonatomic, weak) CAShapeLayer *maskLayer;
@property (nonatomic, weak) CALayer *contentLayer;
@end
@implementation TLMessageImageView
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.contentsCenter = CGRectMake(0.5, 0.6, 0.1, 0.1);
maskLayer.contentsScale = [UIScreen mainScreen].scale; //非常关键设置自动拉伸的效果且不变形
CALayer *contentLayer = [[CALayer alloc] init];
[contentLayer setMask:maskLayer];
[self.layer addSublayer:contentLayer];
self.maskLayer = maskLayer;
self.contentLayer = contentLayer;
}
return self;
}
- (void)dealloc
{
NSLog(@"delloc TLMessageImageView");
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self.maskLayer setFrame:CGRectMake(0, 0, self.width, self.height)];
[self.contentLayer setFrame:CGRectMake(0, 0, self.width, self.height)];
}
- (void)setThumbnailPath:(NSString *)imagePath highDefinitionImageURL:(NSString *)imageURL
{
if (imagePath == nil) {
[self.contentLayer setContents:nil];
}
else {
UIImage *image = [[UIImage imageNamed:imagePath] copy];
[self.contentLayer setContents:(id)(image.CGImage)];
}
}
- (void)setBackgroundImage:(UIImage *)backgroundImage
{
UIImage *image = [backgroundImage copy];
[self.maskLayer setContents:(id)image.CGImage];
}
@end
@@ -0,0 +1,15 @@
//
// TLTextMessageCell.h
// TLChat
//
// Created by 李伯坤 on 16/3/1.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLMessageBaseCell.h"
#import "TLTextMessage.h"
@interface TLTextMessageCell : TLMessageBaseCell
@end
@@ -0,0 +1,88 @@
//
// TLTextMessageCell.m
// TLChat
//
// Created by 李伯坤 on 16/3/1.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLTextMessageCell.h"
#define MSG_SPACE_TOP 14
#define MSG_SPACE_BTM 20
#define MSG_SPACE_LEFT 19
#define MSG_SPACE_RIGHT 22
@interface TLTextMessageCell ()
@property (nonatomic, strong) UILabel *messageLabel;
@end
@implementation TLTextMessageCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self.contentView addSubview:self.messageLabel];
}
return self;
}
- (void)setMessage:(TLTextMessage *)message
{
if (self.message && [self.message.messageID isEqualToString:message.messageID]) {
return;
}
TLMessageOwnerType lastOwnType = self.message ? self.message.ownerTyper : -1;
[super setMessage:message];
[self.messageLabel setAttributedText:[message attrText]];
[self.messageLabel setContentCompressionResistancePriority:500 forAxis:UILayoutConstraintAxisHorizontal];
[self.messageBackgroundView setContentCompressionResistancePriority:100 forAxis:UILayoutConstraintAxisHorizontal];
if (lastOwnType != message.ownerTyper) {
if (message.ownerTyper == TLMessageOwnerTypeSelf) {
[self.messageBackgroundView setImage:[UIImage imageNamed:@"message_sender_bg"]];
[self.messageBackgroundView setHighlightedImage:[UIImage imageNamed:@"message_sender_bgHL"]];
[self.messageLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.messageBackgroundView).mas_offset(-MSG_SPACE_RIGHT);
make.top.mas_equalTo(self.messageBackgroundView).mas_offset(MSG_SPACE_TOP);
}];
[self.messageBackgroundView mas_updateConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.messageLabel).mas_offset(-MSG_SPACE_LEFT);
make.bottom.mas_equalTo(self.messageLabel).mas_offset(MSG_SPACE_BTM);
}];
}
else if (message.ownerTyper == TLMessageOwnerTypeFriend){
[self.messageBackgroundView setImage:[UIImage imageNamed:@"message_receiver_bg"]];
[self.messageBackgroundView setHighlightedImage:[UIImage imageNamed:@"message_receiver_bgHL"]];
[self.messageLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.messageBackgroundView).mas_offset(MSG_SPACE_LEFT);
make.top.mas_equalTo(self.messageBackgroundView).mas_offset(MSG_SPACE_TOP);
}];
[self.messageBackgroundView mas_updateConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.messageLabel).mas_offset(MSG_SPACE_RIGHT);
make.bottom.mas_equalTo(self.messageLabel).mas_offset(MSG_SPACE_BTM);
}];
}
}
[self.messageLabel mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(message.messageFrame.contentSize);
}];
}
#pragma mark - Getter -
- (UILabel *)messageLabel
{
if (_messageLabel == nil) {
_messageLabel = [[UILabel alloc] init];
[_messageLabel setFont:[UIFont fontTextMessageText]];
[_messageLabel setNumberOfLines:0];
}
return _messageLabel;
}
@end
@@ -0,0 +1,19 @@
//
// TLVoiceImageView.h
// TLChat
//
// Created by 李伯坤 on 16/7/11.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface TLVoiceImageView : UIImageView
@property (nonatomic, assign) BOOL isFromMe;
- (void)startPlayingAnimation;
- (void)stopPlayingAnimation;
@end
@@ -0,0 +1,60 @@
//
// TLVoiceImageView.m
// TLChat
//
// Created by 李伯坤 on 16/7/11.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLVoiceImageView.h"
@interface TLVoiceImageView ()
@property (nonatomic, strong) NSArray *imagesArray;
@property (nonatomic, strong) UIImage *normalImage;
@end
@implementation TLVoiceImageView
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setIsFromMe:YES];
}
return self;
}
- (void)setIsFromMe:(BOOL)isFromMe
{
_isFromMe = isFromMe;
if (isFromMe) {
self.imagesArray = @[[UIImage imageNamed:@"message_voice_sender_playing_1"],
[UIImage imageNamed:@"message_voice_sender_playing_2"],
[UIImage imageNamed:@"message_voice_sender_playing_3"]];
self.normalImage = [UIImage imageNamed:@"message_voice_sender_normal"];
}
else {
self.imagesArray = @[[UIImage imageNamed:@"message_voice_receiver_playing_1"],
[UIImage imageNamed:@"message_voice_receiver_playing_2"],
[UIImage imageNamed:@"message_voice_receiver_playing_3"]];
self.normalImage = [UIImage imageNamed:@"message_voice_receiver_normal"];
}
[self setImage:self.normalImage];
}
- (void)startPlayingAnimation
{
self.animationImages = self.imagesArray;
self.animationRepeatCount = 0;
self.animationDuration = 1.0;
[self startAnimating];
}
- (void)stopPlayingAnimation
{
[self stopAnimating];
}
@end
@@ -0,0 +1,14 @@
//
// TLVoiceMessageCell.h
// TLChat
//
// Created by 李伯坤 on 16/5/6.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLMessageBaseCell.h"
#import "TLVoiceMessage.h"
@interface TLVoiceMessageCell : TLMessageBaseCell
@end
@@ -0,0 +1,174 @@
//
// TLVoiceMessageCell.m
// TLChat
//
// Created by 李伯坤 on 16/5/6.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLVoiceMessageCell.h"
#import "TLVoiceImageView.h"
@interface TLVoiceMessageCell ()
@property (nonatomic, strong) UILabel *voiceTimeLabel;
@property (nonatomic, strong) TLVoiceImageView *voiceImageView;
@end
@implementation TLVoiceMessageCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self.contentView addSubview:self.voiceTimeLabel];
[self.messageBackgroundView addSubview:self.voiceImageView];
UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapMsgBGView)];
[self.messageBackgroundView addGestureRecognizer:tapGR];
}
return self;
}
- (void)setMessage:(TLVoiceMessage *)message
{
TLMessageOwnerType lastOwnType = self.message ? self.message.ownerTyper : -1;
[super setMessage:message];
[self.voiceTimeLabel setText:[NSString stringWithFormat:@"%.0lf\"\n", message.time]];
if (lastOwnType != message.ownerTyper) {
if (message.ownerTyper == TLMessageOwnerTypeSelf) {
[self.voiceImageView setIsFromMe:YES];
[self.messageBackgroundView setImage:[UIImage imageNamed:@"message_sender_bg"]];
[self.messageBackgroundView setHighlightedImage:[UIImage imageNamed:@"message_sender_bgHL"]];
[self.voiceTimeLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(self.messageBackgroundView.mas_left);
make.top.mas_equalTo(self.messageBackgroundView.mas_centerY).mas_offset(-5);
}];
[self.voiceImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.mas_equalTo(-13);
make.centerY.mas_equalTo(self.avatarButton);
}];
}
else if (message.ownerTyper == TLMessageOwnerTypeFriend){
[self.voiceImageView setIsFromMe:NO];
[self.messageBackgroundView setImage:[UIImage imageNamed:@"message_receiver_bg"]];
[self.messageBackgroundView setHighlightedImage:[UIImage imageNamed:@"message_receiver_bgHL"]];
[self.voiceTimeLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.messageBackgroundView.mas_right);
make.top.mas_equalTo(self.messageBackgroundView.mas_centerY).mas_offset(-5);
}];
[self.voiceImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(13);
make.centerY.mas_equalTo(self.avatarButton);
}];
}
}
[self.messageBackgroundView mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(message.messageFrame.contentSize);
}];
if (message.msgStatus == TLVoiceMessageStatusRecording) {
[self.voiceTimeLabel setHidden:YES];
[self.voiceImageView setHidden:YES];
[self p_startRecordingAnimation];
}
else {
[self.voiceTimeLabel setHidden:NO];
[self.voiceImageView setHidden:NO];
[self p_stopRecordingAnimation];
[self.messageBackgroundView setAlpha:1.0];
}
message.msgStatus == TLVoiceMessageStatusPlaying ? [self.voiceImageView startPlayingAnimation] : [self.voiceImageView stopPlayingAnimation];
}
- (void)updateMessage:(TLVoiceMessage *)message
{
[super setMessage:message];
[self.voiceTimeLabel setText:[NSString stringWithFormat:@"%.0lf\"\n", message.time]];
if (message.msgStatus == TLVoiceMessageStatusRecording) {
[self.voiceTimeLabel setHidden:YES];
[self.voiceImageView setHidden:YES];
[self p_startRecordingAnimation];
}
else {
[self.voiceTimeLabel setHidden:NO];
[self.voiceImageView setHidden:NO];
[self p_stopRecordingAnimation];
}
message.msgStatus == TLVoiceMessageStatusPlaying ? [self.voiceImageView startPlayingAnimation] : [self.voiceImageView stopPlayingAnimation];
[UIView animateWithDuration:0.5 animations:^{
[self.messageBackgroundView mas_updateConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(message.messageFrame.contentSize);
}];
[self layoutIfNeeded];
}];
}
#pragma mark - # Event Response
- (void)didTapMsgBGView
{
[self.voiceImageView startPlayingAnimation];
if (self.delegate && [self.delegate respondsToSelector:@selector(messageCellTap:)]) {
[self.delegate messageCellTap:self.message];
}
}
#pragma mark - # Private Methods
static bool isStartAnimation = NO;
static float bgAlpha = 1.0;
- (void)p_startRecordingAnimation
{
isStartAnimation = YES;
bgAlpha = 0.4;
[self p_repeatAnimation];
}
- (void)p_repeatAnimation
{
[UIView animateWithDuration:1.0 animations:^{
[self.messageBackgroundView setAlpha:bgAlpha];
} completion:^(BOOL finished) {
if (finished) {
bgAlpha = bgAlpha > 0.9 ? 0.4 : 1.0;
if (isStartAnimation) {
[self p_repeatAnimation];
}
else {
[self.messageBackgroundView setAlpha:1.0];
}
}
}];
}
- (void)p_stopRecordingAnimation
{
isStartAnimation = NO;
}
#pragma mark - # Getter
- (UILabel *)voiceTimeLabel
{
if (_voiceTimeLabel == nil) {
_voiceTimeLabel = [[UILabel alloc] init];
[_voiceTimeLabel setTextColor:[UIColor grayColor]];
[_voiceTimeLabel setFont:[UIFont systemFontOfSize:14.0f]];
}
return _voiceTimeLabel;
}
- (TLVoiceImageView *)voiceImageView
{
if (_voiceImageView == nil) {
_voiceImageView = [[TLVoiceImageView alloc] init];
}
return _voiceImageView;
}
@end
@@ -0,0 +1,19 @@
//
// TLChatMessageDisplayView+Delegate.h
// TLChat
//
// Created by 李伯坤 on 16/3/17.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLChatMessageDisplayView.h"
#import "TLTextMessageCell.h"
#import "TLImageMessageCell.h"
#import "TLExpressionMessageCell.h"
#import "TLVoiceMessageCell.h"
@interface TLChatMessageDisplayView (Delegate) <UITableViewDelegate, UITableViewDataSource, TLMessageCellDelegate, TLActionSheetDelegate>
- (void)registerCellClassForTableView:(UITableView *)tableView;
@end
@@ -0,0 +1,156 @@
//
// TLChatMessageDisplayView+Delegate.m
// TLChat
//
// Created by 李伯坤 on 16/3/17.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLChatMessageDisplayView+Delegate.h"
#import "TLTextDisplayView.h"
#import "TLChatEventStatistics.h"
@implementation TLChatMessageDisplayView (Delegate)
#pragma mark - # Public Methods
- (void)registerCellClassForTableView:(UITableView *)tableView
{
[tableView registerClass:[TLTextMessageCell class] forCellReuseIdentifier:@"TLTextMessageCell"];
[tableView registerClass:[TLImageMessageCell class] forCellReuseIdentifier:@"TLImageMessageCell"];
[tableView registerClass:[TLExpressionMessageCell class] forCellReuseIdentifier:@"TLExpressionMessageCell"];
[tableView registerClass:[TLVoiceMessageCell class] forCellReuseIdentifier:@"TLVoiceMessageCell"];
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"EmptyCell"];
}
#pragma mark - # Delegate
//MARK: UITableViewDataSouce
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TLMessage * message = self.data[indexPath.row];
if (message.messageType == TLMessageTypeText) {
TLTextMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TLTextMessageCell"];
[cell setMessage:message];
[cell setDelegate:self];
return cell;
}
else if (message.messageType == TLMessageTypeImage) {
TLImageMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TLImageMessageCell"];
[cell setMessage:message];
[cell setDelegate:self];
return cell;
}
else if (message.messageType == TLMessageTypeExpression) {
TLExpressionMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TLExpressionMessageCell"];
[cell setMessage:message];
[cell setDelegate:self];
return cell;
}
else if (message.messageType == TLMessageTypeVoice) {
TLVoiceMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TLVoiceMessageCell"];
[cell setMessage:message];
[cell setDelegate:self];
return cell;
}
return [tableView dequeueReusableCellWithIdentifier:@"EmptyCell"];
}
//MARK: UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (indexPath.row >= self.data.count) {
return 0.0f;
}
TLMessage * message = self.data[indexPath.row];
return message.messageFrame.height;
}
//MARK: TLMessageCellDelegate
- (void)messageCellDidClickAvatarForUser:(TLUser *)user
{
if (self.delegate && [self.delegate respondsToSelector:@selector(chatMessageDisplayView:didClickUserAvatar:)]) {
[self.delegate chatMessageDisplayView:self didClickUserAvatar:user];
}
}
- (void)messageCellTap:(TLMessage *)message
{
if (self.delegate && [self.delegate respondsToSelector:@selector(chatMessageDisplayView:didClickMessage:)]) {
[self.delegate chatMessageDisplayView:self didClickMessage:message];
}
}
/**
* 双击Message Cell
*/
- (void)messageCellDoubleClick:(TLMessage *)message
{
if (self.delegate && [self.delegate respondsToSelector:@selector(chatMessageDisplayView:didDoubleClickMessage:)]) {
[self.delegate chatMessageDisplayView:self didDoubleClickMessage:message];
}
}
- (void)messageCellLongPress:(TLMessage *)message rect:(CGRect)rect
{
NSInteger row = [self.data indexOfObject:message];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
if (self.disableLongPressMenu) {
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
return;
}
CGRect cellRect = [self.tableView rectForRowAtIndexPath:indexPath];
rect.origin.y += cellRect.origin.y - self.tableView.contentOffset.y;
__weak typeof(self)weakSelf = self;
[self.menuView showInView:self withMessageType:message.messageType rect:rect actionBlock:^(TLChatMenuItemType type) {
[weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
if (type == TLChatMenuItemTypeCopy) {
NSString *str = [message messageCopy];
[[UIPasteboard generalPasteboard] setString:str];
}
else if (type == TLChatMenuItemTypeDelete) {
TLActionSheet *actionSheet = [[TLActionSheet alloc] initWithTitle:@"是否删除该条消息" delegate:weakSelf cancelButtonTitle:@"取消" destructiveButtonTitle:@"确定" otherButtonTitles: nil];
actionSheet.tag = [weakSelf.data indexOfObject:message];
[actionSheet show];
}
}];
}
//MARK: UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if (self.delegate && [self.delegate respondsToSelector:@selector(chatMessageDisplayViewDidTouched:)]) {
[self.delegate chatMessageDisplayViewDidTouched:self];
}
}
//MARK: TLActionSheetDelegate
- (void)actionSheet:(TLActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
TLMessage * message = [self.data objectAtIndex:actionSheet.tag];
[self p_deleteMessage:message];
}
}
#pragma mark - # Private Methods
- (void)p_deleteMessage:(TLMessage *)message
{
if (self.delegate && [self.delegate respondsToSelector:@selector(chatMessageDisplayView:deleteMessage:)]) {
BOOL ok = [self.delegate chatMessageDisplayView:self deleteMessage:message];
if (ok) {
[self deleteMessage:message withAnimation:YES];
[MobClick event:EVENT_DELETE_MESSAGE];
}
else {
[TLUIUtility showAlertWithTitle:@"错误" message:@"从数据库中删除消息失败。"];
}
}
}
@end
@@ -0,0 +1,65 @@
//
// TLChatMessageDisplayView.h
// TLChat
//
// Created by 李伯坤 on 16/3/9.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "TLChatMessageDisplayViewDelegate.h"
#import "TLChatCellMenuView.h"
#import "TLTextMessage.h"
#import "TLImageMessage.h"
#import "TLExpressionMessage.h"
#import "TLVoiceMessage.h"
@interface TLChatMessageDisplayView : UIView
@property (nonatomic, assign) id<TLChatMessageDisplayViewDelegate>delegate;
@property (nonatomic, strong) NSMutableArray *data;
@property (nonatomic, strong, readonly) UITableView *tableView;
/// 禁用下拉刷新
@property (nonatomic, assign) BOOL disablePullToRefresh;
/// 禁用长按菜单
@property (nonatomic, assign) BOOL disableLongPressMenu;
@property (nonatomic, strong) TLChatCellMenuView *menuView;
/**
* 发送消息(在列表展示)
*/
- (void)addMessage:(TLMessage *)message;
/**
* 删除消息
*/
- (void)deleteMessage:(TLMessage *)message;
- (void)deleteMessage:(TLMessage *)message withAnimation:(BOOL)animation;
/**
* 更新消息状态
*/
- (void)updateMessage:(TLMessage *)message;
- (void)reloadData;
/**
* 滚动到底部
*
* @param animation 是否执行动画
*/
- (void)scrollToBottomWithAnimation:(BOOL)animation;
/**
* 重新加载聊天信息
*/
- (void)resetMessageView;
@end
@@ -0,0 +1,247 @@
//
// TLChatMessageDisplayView.m
// TLChat
//
// Created by 李伯坤 on 16/3/9.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import "TLChatMessageDisplayView.h"
#import "TLChatMessageDisplayView+Delegate.h"
#import <MJRefresh/MJRefresh.h>
#import "TLMessageBaseCell.h"
#import "TLChatEventStatistics.h"
#define PAGE_MESSAGE_COUNT 15
@interface TLChatMessageDisplayView ()
@property (nonatomic, strong) MJRefreshNormalHeader *refresHeader;
/// 用户决定新消息是否显示时间
@property (nonatomic, strong) NSDate *curDate;
@end
@implementation TLChatMessageDisplayView
@synthesize tableView = _tableView;
@synthesize data = _data;
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self addSubview:self.tableView];
[self setDisablePullToRefresh:NO];
[self registerCellClassForTableView:self.tableView];
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(0);
}];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTouchTableView)];
[self.tableView addGestureRecognizer:tap];
[self.tableView addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
return self;
}
- (void)dealloc
{
[self.menuView dismiss];
[self.tableView removeObserver:self forKeyPath:@"bounds"];
#ifdef DEBUG_MEMERY
NSLog(@"dealloc MessageDisplayView");
#endif
}
#pragma mark - # Public Methods
- (void)resetMessageView
{
[self.data removeAllObjects];
[self.tableView reloadData];
self.curDate = [NSDate date];
if (!self.disablePullToRefresh) {
[self.tableView setMj_header:self.refresHeader];
}
TLWeakSelf(self);
[self p_tryToRefreshMoreRecord:^(NSInteger count, BOOL hasMore) {
if (!hasMore) {
weakself.tableView.mj_header = nil;
}
if (count > 0) {
[weakself.tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[weakself.tableView scrollToBottomWithAnimation:NO];
});
}
}];
}
- (void)addMessage:(TLMessage *)message
{
[self.data addObject:message];
[self.tableView reloadData];
}
- (void)setData:(NSMutableArray *)data
{
_data = data;
[self.tableView reloadData];
}
- (void)deleteMessage:(TLMessage *)message
{
[self deleteMessage:message withAnimation:YES];
}
- (void)deleteMessage:(TLMessage *)message withAnimation:(BOOL)animation
{
if (message == nil) {
return;
}
NSInteger index = [self.data indexOfObject:message];
if (self.delegate && [self.delegate respondsToSelector:@selector(chatMessageDisplayView:deleteMessage:)]) {
BOOL ok = [self.delegate chatMessageDisplayView:self deleteMessage:message];
if (ok) {
[self.data removeObject:message];
[self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:index inSection:0]] withRowAnimation:animation ? UITableViewRowAnimationAutomatic : UITableViewRowAnimationNone];
[MobClick event:EVENT_DELETE_MESSAGE];
}
else {
[TLUIUtility showAlertWithTitle:@"错误" message:@"从数据库中删除消息失败。"];
}
}
}
- (void)updateMessage:(TLMessage *)message
{
NSArray *visibleCells = [self.tableView visibleCells];
for (id cell in visibleCells) {
if ([cell isKindOfClass:[TLMessageBaseCell class]]) {
if ([[(TLMessageBaseCell *)cell message].messageID isEqualToString:message.messageID]) {
[cell updateMessage:message];
return;
}
}
}
}
- (void)reloadData
{
[self.tableView reloadData];
}
- (void)scrollToBottomWithAnimation:(BOOL)animation
{
[self.tableView scrollToBottomWithAnimation:animation];
}
- (void)setDisablePullToRefresh:(BOOL)disablePullToRefresh
{
if (disablePullToRefresh) {
[self.tableView setMj_header:nil];
}
else {
[self.tableView setMj_header:self.refresHeader];
}
}
#pragma mark - # Event Response
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if (object == self.tableView && [keyPath isEqualToString:@"bounds"]) { // tableView变小时,消息贴底
CGRect oldBounds, newBounds;
[change[@"old"] getValue:&oldBounds];
[change[@"new"] getValue:&newBounds];
CGFloat t = oldBounds.size.height - newBounds.size.height;
if (t > 0 && fabs(self.tableView.contentOffset.y + t + newBounds.size.height - self.tableView.contentSize.height) < 1.0) {
[self scrollToBottomWithAnimation:NO];
}
}
}
- (void)didTouchTableView
{
if (self.delegate && [self.delegate respondsToSelector:@selector(chatMessageDisplayViewDidTouched:)]) {
[self.delegate chatMessageDisplayViewDidTouched:self];
}
}
#pragma mark - # Private Methods
/**
* 获取聊天历史记录
*/
- (void)p_tryToRefreshMoreRecord:(void (^)(NSInteger count, BOOL hasMore))complete
{
TLWeakSelf(self);
if (self.delegate && [self.delegate respondsToSelector:@selector(chatMessageDisplayView:getRecordsFromDate:count:completed:)]) {
[self.delegate chatMessageDisplayView:self
getRecordsFromDate:self.curDate
count:PAGE_MESSAGE_COUNT
completed:^(NSDate *date, NSArray *array, BOOL hasMore) {
if (array.count > 0 && [date isEqualToDate:weakself.curDate]) {
weakself.curDate = [array[0] date];
[weakself.data insertObjects:array atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, array.count)]];
complete(array.count, hasMore);
}
else {
complete(0, hasMore);
}
}];
}
}
#pragma mark - # Getter
- (UITableView *)tableView
{
if (_tableView == nil) {
_tableView = [[UITableView alloc] init];
[_tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
[_tableView setBackgroundColor:[UIColor clearColor]];
[_tableView setTableFooterView:[[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 10)]];
[_tableView setDelegate:self];
[_tableView setDataSource:self];
}
return _tableView;
}
- (TLChatCellMenuView *)menuView
{
if (!_menuView) {
_menuView = [[TLChatCellMenuView alloc] init];
}
return _menuView;
}
- (NSMutableArray *)data
{
if (_data == nil) {
_data = [[NSMutableArray alloc] init];
}
return _data;
}
- (MJRefreshNormalHeader *)refresHeader
{
if (_refresHeader == nil) {
TLWeakSelf(self);
_refresHeader = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
[weakself p_tryToRefreshMoreRecord:^(NSInteger count, BOOL hasMore) {
[weakself.tableView.mj_header endRefreshing];
if (!hasMore) {
weakself.tableView.mj_header = nil;
}
if (count > 0) {
[weakself.tableView reloadData];
[weakself.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:count inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
}
}];
}];
_refresHeader.lastUpdatedTimeLabel.hidden = YES;
_refresHeader.stateLabel.hidden = YES;
}
return _refresHeader;
}
@end
@@ -0,0 +1,61 @@
//
// TLChatMessageDisplayViewDelegate.h
// TLChat
//
// Created by 李伯坤 on 16/3/23.
// Copyright © 2016年 李伯坤. All rights reserved.
//
#import <Foundation/Foundation.h>
@class TLUser;
@class TLMessage;
@class TLChatMessageDisplayView;
@protocol TLChatMessageDisplayViewDelegate <NSObject>
/**
* 聊天界面点击事件,用于收键盘
*/
- (void)chatMessageDisplayViewDidTouched:(TLChatMessageDisplayView *)chatTVC;
/**
* 下拉刷新,获取某个时间段的聊天记录(异步)
*
* @param chatTVC chatTVC
* @param date 开始时间
* @param count 条数
* @param completed 结果Blcok
*/
- (void)chatMessageDisplayView:(TLChatMessageDisplayView *)chatTVC
getRecordsFromDate:(NSDate *)date
count:(NSUInteger)count
completed:(void (^)(NSDate *, NSArray *, BOOL))completed;
@optional
/**
* 消息长按删除
*
* @return 删除是否成功
*/
- (BOOL)chatMessageDisplayView:(TLChatMessageDisplayView *)chatTVC
deleteMessage:(TLMessage *)message;
/**
* 用户头像点击事件
*/
- (void)chatMessageDisplayView:(TLChatMessageDisplayView *)chatTVC
didClickUserAvatar:(TLUser *)user;
/**
* Message点击事件
*/
- (void)chatMessageDisplayView:(TLChatMessageDisplayView *)chatTVC
didClickMessage:(TLMessage *)message;
/**
* Message双击事件
*/
- (void)chatMessageDisplayView:(TLChatMessageDisplayView *)chatTVC
didDoubleClickMessage:(TLMessage *)message;
@end