局域网内端到端的聊天项目(二)

  • 上一篇已实现键盘工具类的输入及表情键盘
  • 接下来进行聊天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

你可能感兴趣的:(局域网内端到端的聊天项目(二))