- 上一篇已实现键盘工具类的输入及表情键盘
- 接下来进行聊天cell的布局
- 图文混排的实现
- 键盘工具的显示/收起 及对应的位置滑动
一.聊天cell的布局(文本cell)
- 使用同一个cell
- 用户头像/名称/消息内容
- 消息分为自己发送及朋友发送两种类型
- 根据消息状态来显示隐藏及切换不同的聊天背景图
1EC7ACC8-24E8-4016-A7D7-DD63E3BF154C.png
ChatMessageCell 聊天cell
@interface ChatMessageCell ()
/// 朋友消息的背景图
@property (strong, nonatomic) UIImage *friendMessageImage;
/// 自己消息的背景图
@property (strong, nonatomic) UIImage *meMessageImage;
@property (strong, nonatomic) UIImageView *userIconImageView;
@property (strong, nonatomic) UILabel *userNameLabel;
@property (strong, nonatomic) UIImageView *messageBgImageView;
@property (strong, nonatomic) UILabel *messageLabel;
@property (strong, nonatomic) UIImageView *meIconImageView;
@end
// 外界模型的赋值操作
- (void)setDataModel:(ChatMessageModel *)dataModel{
_dataModel = dataModel;
[_messageBgImageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(dataModel.messageW);
make.top.mas_equalTo(self.userNameLabel.mas_bottom).offset(6);
make.bottom.mas_equalTo(self.mas_bottom).offset(-8);
if (dataModel.isFormMe) {
make.right.mas_equalTo(self.meIconImageView.mas_left).offset(-15);
}else{
make.left.mas_equalTo(self.userIconImageView.mas_right).offset(15);
}
}];
[_messageLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.mas_equalTo(self.messageBgImageView);
make.left.mas_equalTo(self.messageBgImageView.mas_left).offset(dataModel.isFormMe ? messageLabelForHeadRightMargin : messageLabelForHeadLeftMargin);
make.right.mas_equalTo(self.messageBgImageView.mas_right).offset(dataModel.isFormMe ? -messageLabelForHeadLeftMargin : -messageLabelForHeadRightMargin);
}];
self.userIconImageView.hidden = _dataModel.isFormMe ? YES : NO;
self.meIconImageView.hidden = _dataModel.isFormMe ? NO : YES;
self.messageBgImageView.image = _dataModel.isFormMe ? self.meMessageImage : self.friendMessageImage;
self.userNameLabel.textAlignment = _dataModel.isFormMe ? NSTextAlignmentRight : NSTextAlignmentLeft;
//self.messageLabel.text = _dataModel.messageContent;
self.messageLabel.attributedText = _dataModel.messageContentAttributed;
self.userNameLabel.text = _dataModel.userName;
}
// 聊天背景图
- (UIImage *)friendMessageImage{
if (!_friendMessageImage) {
_friendMessageImage = [self imageResizabelWithName:@"chat_reciver_new"];
}
return _friendMessageImage;
}
- (UIImage *)meMessageImage{
if (!_meMessageImage) {
_meMessageImage = [self imageResizabelWithName:@"chat_send_new"];
}
return _meMessageImage;
}
// 返回一张四周不被拉伸的图片
- (UIImage *)imageResizabelWithName:(NSString *)imageName{
UIImage *img = [UIImage imageNamed:imageName];
CGFloat left = img.size.width * 0.5;
CGFloat tb = img.size.height * 0.5;
return [img resizableImageWithCapInsets:UIEdgeInsetsMake(left, tb, tb,left) resizingMode:UIImageResizingModeTile];
}
保护图片四周不被拉伸的方法
- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode
实现效果:
IMG_2415(20171127-105836).jpg
二.图文混排的实现
- 图文混排使用了三方正则表达式库 RegexKitLite
- RegexKitLite 是mrc的需要配置 (-fno-objc-arc) 同时需要在Other Linker Flags 配置 (-licucore)不然会有一堆报错
转换包含表情文字的内容
+ (NSMutableAttributedString *)emoticonAttributedWithText:(NSString *)text font:(UIFont *)font
{
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] init];
// 表情的规则
NSString *emotionPattern = @"\\[[0-9a-zA-Z\\u4e00-\\u9fa5]+\\]";
// @的规则
// NSString *atPattern = @"@[0-9a-zA-Z\\u4e00-\\u9fa5-_]+";
// #话题#的规则
// NSString *topicPattern = @"#[0-9a-zA-Z\\u4e00-\\u9fa5]+#";
// url链接的规则
// NSString *urlPattern = @"\\b(([\\w-]+://?|www[.])[^\\s()<>]+(?:\\([\\w\\d]+\\)|([^[:punct:]\\s]|/)))";
// 遍历所有的特殊字符串 <此处仅仅遍历表情>
NSMutableArray *parts = [NSMutableArray array];
[text enumerateStringsMatchedByRegex:emotionPattern usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
if ((*capturedRanges).length == 0) return ;
HJTextPart *part = [[HJTextPart alloc] init];
part.text = *capturedStrings; // 表情对应的文字
part.range = *capturedRanges;
part.emotion = YES;
[parts addObject:part];
}];
// 遍历所有非特殊字符
[text enumerateStringsSeparatedByRegex:emotionPattern usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
HJTextPart *part = [[HJTextPart alloc] init];
part.text = *capturedStrings; // 表情对应的文字
part.range = *capturedRanges;
[parts addObject:part];
}];
// 排序
[parts sortUsingComparator:^NSComparisonResult(HJTextPart *part1, HJTextPart *part2) {
if (part1.range.location > part2.range.location) {
return NSOrderedDescending;
}
return NSOrderedAscending;
}];
// 遍历对应的表情
ESEmoticonTool *emoticonTool = [[ESEmoticonTool alloc] init];
for (HJTextPart *part in parts) {
NSAttributedString *subStr = nil;
if (part.isEmotion) { // 如果是表情
NSString *imageName = [emoticonTool hj_emticonImageNameByEmticonName:part.text];
NSTextAttachment *attch = [[NSTextAttachment alloc] init];
if (imageName) {
UIImage *image = [UIImage imageNamed:imageName];
attch.image = image;
attch.bounds = CGRectMake(0, -3, font.lineHeight, font.lineHeight);
subStr = [NSAttributedString attributedStringWithAttachment:attch];
}else{
subStr = [[NSAttributedString alloc] initWithString:part.text];
}
}else{ // 非表情字符串
subStr = [[NSAttributedString alloc] initWithString:part.text];
}
[attributedText appendAttributedString:subStr];
}
// 设置文字大小 保证计算的高度正确
[attributedText addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, attributedText.length)];
return attributedText;
}
实现如图:
IMG_2416(20171127-113058).jpg
三.键盘工具的显示/收起
- 可以根据scrollView的上拉弹出键盘 下拉收起键盘
- 点击键盘输入框弹出键盘
- 监听textView代理回调 同时回调出去进行位置的调整
#pragma mark - table view delegate
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
if (velocity.y >= 0.1) {
[self.keyBoardToolView showKeyBoard];
}else if(velocity.y < 0){
[self.keyBoardToolView exitKeyBoard];
}
}
#pragma mark - ESKeyBoardToolViewDelegate
// 键盘弹出时的回调
- (void)ESKeyBoardToolViewDidEditing:(ESKeyBoardToolView *)view changeY:(CGFloat)yValue{
dispatch_async(dispatch_get_main_queue(), ^{
CGFloat contentH = self.tableView.contentSize.height;
if (yValue == 0) { // 键盘弹出的时候
CGFloat offsetY = contentH - self.tableView.hj_height + view.systemKeyboardH;
if (offsetY > 0) {
[self.tableView setContentOffset:CGPointMake(self.tableView.contentOffset.x, offsetY) animated:YES];
}
}else{
// 此处需判断 导航条是否存在
self.orginalOffsetY = NAVBARH;
CGFloat showH = view.y - self.orginalOffsetY;
CGFloat needOffsetY = contentH - showH;
if (needOffsetY >= 0) {
[self.tableView setContentOffset:CGPointMake(self.tableView.contentOffset.x, needOffsetY-self.orginalOffsetY) animated:NO];
}
}
});
}
- (void)ESKeyBoardToolViewDidEndEdit:(ESKeyBoardToolView *)view{
[self scrollToLastCellAnimated:YES];
}
- (void)scrollToLastCellAnimated:(BOOL)animated{
if (self.messageItems.count <= 0) {
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
CGFloat contentH = self.tableView.contentSize.height;
CGFloat showH = 0;
if (self.keyBoardToolView.inputTextView.isFirstResponder) {
showH = self.tableView.hj_height - self.keyBoardToolView.systemKeyboardH;
}else{
showH = self.tableView.hj_height;
}
CGFloat needOffsetY = (contentH - showH);
if (needOffsetY > 0) {
[self.tableView setContentOffset:CGPointMake(self.tableView.contentOffset.x, needOffsetY) animated:animated];
}
});
});
}
// ESKeyBoardToolView.m 中的实现
// 退出键盘
- (void)exitKeyBoard{
if (self.isEditing) {
return;
}
[self endEditing:YES];
}
// 弹出键盘
- (void)showKeyBoard{
if (!self.inputTextView.isFirstResponder) {
[self.inputTextView becomeFirstResponder];
}
}
#pragma mark - textView delegate
- (void)textViewDidBeginEditing:(UITextView *)textView{
if ([self.delegate respondsToSelector:@selector(ESKeyBoardToolViewDidEditing:changeY:)]) {
[self.delegate ESKeyBoardToolViewDidEditing:self changeY:0];
}
}
- (void)textViewDidEndEditing:(UITextView *)textView{
self.isEditing = NO;
if ([self.delegate respondsToSelector:@selector(ESKeyBoardToolViewDidEndEdit:)]) {
[self.delegate ESKeyBoardToolViewDidEndEdit:self];
}
[self exitKeyBoardInputView];
}
效果图如下:
IMG_2418.GIF