// // chatCellView.m // AIIM // // Created by gan on 2025/6/24. // #import #import "chatCellView.h" #import "ChatLongPressMenuView.h" #import "ChatPopoverView.h" #import "CryptoAES.h" #import #import #import #import #import "config.h" #import "FilePreviewer.h" #import "ChatRecordController.h" #import "QuoteDetailController.h" #import "UILabel+YBAttributeTextTapAction.h" #import // 定义一些常量 static const CGFloat kTextVerticalMargin = 10.0f; // 内容垂直边距 static const CGFloat kTextHorizontalMargin = 12.0f; //文本水平边距 static const CGFloat kVerticalMargin = 18.0f; // 垂直边距 static const CGFloat kContentMaxWidth = 220.0f; // 内容最大宽度 static const CGFloat kAvatarSize = 36.0f; // 头像大小 static const CGFloat kAvatarMargin = 20.0f; // 头像边距 static const CGFloat kAvatarBatchMargin = 70.0f; // 批量操作头像边距 static const CGFloat kBubbleTopMargin = 20.0f; // 气泡顶部距离 static const CGFloat kBubbleBottomMargin = 40.0f; // 气泡底部距离 static const CGFloat kBubbleArrowWidth = 6.0f; // 气泡箭头宽度 static const CGFloat kMinBubbleHeight = 18.0f; // 气泡最小高度 static const CGFloat kMediaCornerRadius = 4.0f; @interface chatCellView() // 基本视图 @property (nonatomic, strong) UIImageView *avatarImageView; @property (nonatomic, strong) UILabel *nameLabel; @property (nonatomic, strong) UIImageView *bubbleImageView; @property (nonatomic, strong) UILabel * timeLabel; @property (nonatomic, strong) UILabel * readStateLbl; @property (nonatomic, strong) UIButton * batchStateBtn; // 内容视图 @property (nonatomic, strong) UILabel *textLbl; @property (nonatomic, strong) UIImageView *imageContentView; @property (nonatomic, strong) UIView *videoContentView; @property (nonatomic, strong) UIImageView *thumbnailView; @property (nonatomic, strong) UIImageView *loadingImg; @property (nonatomic, strong) UIView *fileContentView; @property (nonatomic, strong) UIView *voiceContentView; @property (nonatomic, strong) UIView *callContentView; @property (nonatomic, strong) UIView *historyContentView; @property (nonatomic, strong) UILabel * eventLbl; @property (nonatomic, strong) UILabel * callbackLbl; @property (nonatomic, strong) UILabel * deleateLbl; @property (nonatomic, strong) UILabel * quoteLbl; @property (nonatomic, strong) UILabel * jinduLbl; @property (strong, nonatomic) AVPlayer *player; @property (strong, nonatomic) AVPlayerViewController *playerViewController; // 当前显示的内容视图 @property (nonatomic, weak) UIView *currentContentView; @property (nonatomic,strong) NSArray *matchesArray; @end @implementation chatCellView - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { [self initAlllSubviews]; self.selectionStyle = UITableViewCellSelectionStyleNone; self.backgroundColor = [UIColor clearColor]; } return self; } -(void)initAlllSubviews{ //复选框 _batchStateBtn = [UIButton buttonWithType:UIButtonTypeCustom]; [_batchStateBtn setBackgroundImage:kImageMake(@"checkbox_unselected") forState:UIControlStateNormal]; [_batchStateBtn setBackgroundImage:kImageMake(@"checkbox_selected") forState:UIControlStateSelected]; _batchStateBtn.hidden = YES; [self.contentView addSubview:_batchStateBtn]; [_batchStateBtn addTarget:self action:@selector(batchStateButtonClicked) forControlEvents:UIControlEventTouchUpInside]; // 头像 _avatarImageView = [UIImageView new]; _avatarImageView.layer.cornerRadius = kAvatarSize / 2; _avatarImageView.layer.masksToBounds = YES; _avatarImageView.userInteractionEnabled = YES; _avatarImageView.contentMode = UIViewContentModeScaleAspectFill; [self.contentView addSubview:_avatarImageView]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleAvatarTap)]; [_avatarImageView addGestureRecognizer:tap]; // [_avatarImageView jk_addLongPressActionWithBlock:^(UIGestureRecognizer *gestureRecoginzer) { // [self handleAvatarLongPress]; // }]; // 名字 _nameLabel = [UILabel new]; _nameLabel.font = SYSFONT(12); _nameLabel.textColor = [UIColor whiteColor]; [self.contentView addSubview:_nameLabel]; // 时间 _timeLabel = [UILabel new]; _timeLabel.font = SYSFONT(12); _timeLabel.textColor = UIColor.whiteColor; [self.contentView addSubview:_timeLabel]; _readStateLbl = [UILabel new]; _readStateLbl.font = SYSFONT(12); _readStateLbl.textColor = UIColor.whiteColor; [self.contentView addSubview:_readStateLbl]; // 气泡背景 _bubbleImageView = [UIImageView new]; _bubbleImageView.userInteractionEnabled = YES; [self.contentView addSubview:_bubbleImageView]; // 初始化所有内容视图 [self setupTextContentView]; [self setupImageContentView]; [self setupVideoContentView]; [self setupFileContentView]; [self setupVoiceContentView]; [self setupCallContentView]; [self setupHistoryContentView]; [self setupEventContentView]; [self setupCallbackContentView]; [self setupdeleateContentView]; } #pragma mark - 内容视图配置 - (void)setupTextContentView { _textLbl = [UILabel new]; _textLbl.numberOfLines = 0; _textLbl.textColor = UIColor.blackColor; _textLbl.userInteractionEnabled = YES; // 允许用户交互 [self.contentView addSubview:_textLbl]; // UITapGestureRecognizer *taptext = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(labelTapped:)]; // [_textLbl addGestureRecognizer:taptext]; _quoteLbl = [UILabel new]; _quoteLbl.numberOfLines = 1; _quoteLbl.font = SYSFONT(16); _quoteLbl.textColor = UIColor.whiteColor; _quoteLbl.layer.cornerRadius = 2.f; _quoteLbl.layer.masksToBounds = YES; _quoteLbl.userInteractionEnabled = YES; // 允许用户交互 [self.contentView addSubview:_quoteLbl]; // _quoteLbl.backgroundColor = [UIColor redColor]; // 添加点击手势 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleQuoteTap)]; [_quoteLbl addGestureRecognizer:tap]; [self.contentView bringSubviewToFront:_quoteLbl]; } - (void)setupImageContentView { _imageContentView = [UIImageView new]; _imageContentView.contentMode = UIViewContentModeScaleAspectFit; _imageContentView.clipsToBounds = YES; _imageContentView.layer.cornerRadius = kMediaCornerRadius; _imageContentView.layer.masksToBounds = YES; _imageContentView.userInteractionEnabled = YES; [self.contentView addSubview:_imageContentView]; // 添加点击手势 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleImageTap)]; [_imageContentView addGestureRecognizer:tap]; } - (void)setupVideoContentView { _videoContentView = [UIView new]; _videoContentView.layer.cornerRadius = kMediaCornerRadius; _videoContentView.layer.masksToBounds = YES; _videoContentView.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0]; _videoContentView.userInteractionEnabled = YES; _thumbnailView = [[UIImageView alloc] init]; _thumbnailView.contentMode = UIViewContentModeScaleAspectFill; _thumbnailView.clipsToBounds = YES; _thumbnailView.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0]; [_videoContentView addSubview:_thumbnailView]; [_thumbnailView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(_videoContentView); }]; NSString *gifPath = [[NSBundle mainBundle] pathForResource:@"load" ofType:@"gif"]; NSData *gifData = [NSData dataWithContentsOfFile:gifPath]; UIImage *placeholder = [UIImage sd_imageWithGIFData:gifData]; _loadingImg = [[UIImageView alloc] initWithImage:placeholder]; [_videoContentView addSubview:_loadingImg]; [_loadingImg mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(_videoContentView); make.width.height.mas_equalTo(100); }]; _loadingImg.alpha = 0; UIImageView *playIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"play_btn"]]; [_videoContentView addSubview:playIcon]; [playIcon mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(_videoContentView); make.width.height.mas_equalTo(40); }]; _jinduLbl = [UILabel new]; [_videoContentView addSubview:_jinduLbl]; _jinduLbl.alpha = 0; _jinduLbl.numberOfLines = 1; _jinduLbl.font = SYSFONT(16); _jinduLbl.textColor = UIColor.whiteColor; _jinduLbl.layer.cornerRadius = 20.f; _jinduLbl.layer.masksToBounds = YES; _jinduLbl.textAlignment = NSTextAlignmentCenter; _jinduLbl.backgroundColor =[UIColor colorWithRed:0 green:0 blue:0 alpha:0.7]; _jinduLbl.text = @"99"; [_jinduLbl mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_videoContentView).offset(8); make.top.equalTo(_videoContentView).offset(8); make.width.height.mas_equalTo(40); }]; [self.contentView addSubview:_videoContentView]; // 添加点击手势 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleVideoTap)]; [_videoContentView addGestureRecognizer:tap]; } - (void)setupFileContentView { _fileContentView = [UIView new]; UIImageView *iconView = [[UIImageView alloc] init]; UILabel *nameLabel = [[UILabel alloc] init]; nameLabel.textColor = UIColor.blackColor; nameLabel.font = [UIFont systemFontOfSize:14]; nameLabel.numberOfLines = 1; UILabel *sizeLabel = [[UILabel alloc] init]; sizeLabel.font = [UIFont systemFontOfSize:12]; sizeLabel.textColor = [UIColor grayColor]; [_fileContentView addSubview:iconView]; [_fileContentView addSubview:nameLabel]; [_fileContentView addSubview:sizeLabel]; [iconView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_fileContentView).offset(8); make.centerY.equalTo(_fileContentView); make.width.height.mas_equalTo(40); }]; [nameLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(iconView.mas_right).offset(8); make.top.equalTo(_fileContentView).offset(8); make.right.equalTo(_fileContentView).offset(-8); }]; [sizeLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(nameLabel); make.top.equalTo(nameLabel.mas_bottom).offset(4); // make.right.equalTo(nameLabel); // make.bottom.lessThanOrEqualTo(_fileContentView).offset(-8); }]; [self.contentView addSubview:_fileContentView]; // 添加点击手势 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleFileTap)]; [_fileContentView addGestureRecognizer:tap]; } - (void)setupVoiceContentView { _voiceContentView = [UIView new]; UIImageView *voiceIcon = [[UIImageView alloc] initWithImage:kImageMake(@"play_btn")]; UILabel *durationLabel = [[UILabel alloc] init]; durationLabel.textColor = UIColor.blackColor; durationLabel.font = [UIFont systemFontOfSize:14]; [_voiceContentView addSubview:voiceIcon]; [_voiceContentView addSubview:durationLabel]; [voiceIcon mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(_voiceContentView); make.width.height.mas_equalTo(20); }]; [durationLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(_voiceContentView); make.left.equalTo(voiceIcon.mas_right).offset(8); }]; [self.contentView addSubview:_voiceContentView]; // 添加点击手势 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleVoiceTap)]; [_voiceContentView addGestureRecognizer:tap]; } - (void)setupCallContentView { _callContentView = [UIView new]; UIImageView *callIcon = [[UIImageView alloc] init]; UILabel *callLabel = [[UILabel alloc] init]; callLabel.textColor = UIColor.blackColor; callLabel.font = [UIFont systemFontOfSize:16]; UILabel *durationLabel = [[UILabel alloc] init]; durationLabel.font = [UIFont systemFontOfSize:16]; durationLabel.textColor = [UIColor grayColor]; [_callContentView addSubview:callIcon]; [_callContentView addSubview:callLabel]; [_callContentView addSubview:durationLabel]; // [callIcon mas_makeConstraints:^(MASConstraintMaker *make) { // make.left.equalTo(_callContentView).offset(8); // make.centerY.equalTo(_callContentView); // make.width.height.mas_equalTo(20); // }]; // // [callLabel mas_makeConstraints:^(MASConstraintMaker *make) { // make.left.equalTo(callIcon.mas_right).offset(8); // make.top.equalTo(_callContentView).offset(8); //// make.width.mas_lessThanOrEqualTo(70); //// make.right.equalTo(_callContentView).offset(-8); // }]; // // [durationLabel mas_makeConstraints:^(MASConstraintMaker *make) { // make.left.equalTo(callLabel.mas_right).offset(8); // make.top.equalTo(callLabel); // make.right.equalTo(_callContentView).offset(-8); // make.bottom.equalTo(_callContentView).offset(-8); // }]; [self.contentView addSubview:_callContentView]; // 添加点击手势 // UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleCallTap)]; // [_callContentView addGestureRecognizer:tap]; } - (void)setupHistoryContentView { _historyContentView = [UIView new]; UILabel *titleLabel = [[UILabel alloc] init]; titleLabel.font = SYSBFONT(14); titleLabel.text = @"聊天记录"; titleLabel.textColor = UIColor.blackColor; UILabel *contentLabel = [[UILabel alloc] init]; contentLabel.font = SYSFONT(12); contentLabel.numberOfLines = 6; // contentLabel.displaysAsynchronously = YES; [_historyContentView addSubview:titleLabel]; [_historyContentView addSubview:contentLabel]; [titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.left.top.equalTo(_historyContentView).offset(8); make.right.equalTo(_historyContentView).offset(-8); make.height.mas_equalTo(14); }]; [contentLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(titleLabel); make.top.equalTo(titleLabel.mas_bottom).offset(4); make.bottom.equalTo(_historyContentView).offset(-8); }]; [self.contentView addSubview:_historyContentView]; // 添加点击手势 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleHistoryTap)]; [_historyContentView addGestureRecognizer:tap]; } - (void)setupEventContentView { _eventLbl = [[UILabel alloc] init]; _eventLbl.font = SYSFONT(14); _eventLbl.textColor = UIColor.whiteColor; _eventLbl.layer.cornerRadius = 2.f; _eventLbl.layer.masksToBounds = YES; [self.contentView addSubview:_eventLbl]; } - (void)setupCallbackContentView { _callbackLbl = [[UILabel alloc] init]; _callbackLbl.font = SYSFONT(14); _callbackLbl.textColor = UIColor.whiteColor; _callbackLbl.layer.cornerRadius = 2.f; _callbackLbl.layer.masksToBounds = YES; [self.contentView addSubview:_callbackLbl]; } - (void)setupdeleateContentView { _deleateLbl = [[UILabel alloc] init]; _deleateLbl.font = SYSFONT(14); _deleateLbl.textColor = UIColor.blackColor; _deleateLbl.layer.cornerRadius = 2.f; _deleateLbl.layer.masksToBounds = YES; [self.contentView addSubview:_deleateLbl]; } #pragma mark - 数据绑定 - (void)setMessageModel:(ChatMessageModel *)messageModel { // NSLog(@"messageModel:%@",messageModel); _messageModel = messageModel; // 隐藏所有内容视图 _textLbl.hidden = YES; _quoteLbl.hidden = YES; _imageContentView.hidden = YES; _videoContentView.hidden = YES; _fileContentView.hidden = YES; _voiceContentView.hidden = YES; _callContentView.hidden = YES; _historyContentView.hidden = YES; _eventLbl.hidden = YES; _callbackLbl.hidden = YES; _deleateLbl.hidden = YES; _readStateLbl.hidden = YES; // 根据消息类型配置内容 switch (messageModel.messageType) { case ChatMessageTypeText: [self setupTextContent]; break; case ChatMessageTypeImage: [self setupImageContent]; break; case ChatMessageTypeVideo: [self setupVideoContent]; break; case ChatMessageTypeFile: [self setupFileContent]; break; case ChatMessageTypeCallBack: //撤回 [self setupCallbackContent]; break; case ChatMessageTypeVoice: [self setupVoiceContent]; break; case ChatMessageTypeCall: [self setupCallContent]; break; case ChatMessageTypeHistory: [self setupHistoryContent]; break; case ChatMessageTypeEvent: [self setupEventContent]; break; case ChatMessageTypeCallBack2: //撤回PC [self setupCallbackContent]; break; case ChatMessageTypeDel: [self setupDeleateContent]; break; } // 配置头像和名字 [self setupAvatarAndName]; // 配置气泡背景 [self setupBubbleBackground]; // 更新布局 [self layoutContentViews]; //长按事件 [self setupLongPressEvent]; } #pragma mark - 批量状态 - (void)setBatchState:(BOOL)batchState{ if (_messageModel.messageType == ChatMessageTypeEvent || _messageModel.messageType == ChatMessageTypeCallBack || _messageModel.messageType == ChatMessageTypeCallBack2) { self.batchStateBtn.hidden = YES; return; } _batchState = batchState; self.batchStateBtn.hidden = !batchState; if (!_messageModel.isSender) { if (batchState) { [_avatarImageView mas_remakeConstraints:^(MASConstraintMaker *make) { make.size.mas_equalTo(CGSizeMake(kAvatarSize, kAvatarSize)); make.left.mas_equalTo(kAvatarBatchMargin); make.top.equalTo(self.contentView).offset(kVerticalMargin); }]; }else{ [_avatarImageView mas_remakeConstraints:^(MASConstraintMaker *make) { make.size.mas_equalTo(CGSizeMake(kAvatarSize, kAvatarSize)); make.left.mas_equalTo(kAvatarMargin); make.top.equalTo(self.contentView).offset(kVerticalMargin); }]; } } self.batchStateBtn.selected = NO; } - (void)setupAvatarAndName { self.readStateLbl.text = nil; // 这里应该根据实际数据设置头像和名字 if (_messageModel.avatar) { [_avatarImageView sd_setImageWithURL:getURL(_messageModel.avatar) placeholderImage:kImageMake(@"Avatar")]; }else{ _avatarImageView.image = kImageMake(@"Avatar"); } _timeLabel.text = _messageModel.formatterTime; _readStateLbl.text = _messageModel.readStatus; if(_messageModel.readstate==4){ _readStateLbl.textColor = globalColor(GCTypeOrangeR); } else{ _readStateLbl.textColor = [UIColor whiteColor]; } if (_messageModel.type==1) { if (_messageModel.nickName && ![_messageModel.nickName isKindOfClass:NSNull.class]) { _nameLabel.text = _messageModel.isSender ? @"": _messageModel.nickName; _nameLabel.textAlignment = _messageModel.isSender ? NSTextAlignmentRight : NSTextAlignmentLeft; } } if (_messageModel.messageType == ChatMessageTypeEvent || _messageModel.messageType == ChatMessageTypeCallBack || _messageModel.messageType == ChatMessageTypeCallBack2) { _avatarImageView.hidden = YES; _nameLabel.hidden = YES; _readStateLbl.hidden = YES; _timeLabel.hidden = YES; }else{ _avatarImageView.hidden = NO; _nameLabel.hidden = NO; _readStateLbl.hidden = NO; _timeLabel.hidden = NO; } if (_messageModel.type == 0&& _messageModel.isSender) { _readStateLbl.hidden = NO; }else{ _readStateLbl.hidden = YES; } } - (void)setupBubbleBackground { // 图片和视频类型不需要气泡背景 if (_messageModel.messageType == ChatMessageTypeImage || _messageModel.messageType == ChatMessageTypeVideo || _messageModel.messageType == ChatMessageTypeEvent|| _messageModel.messageType == ChatMessageTypeCallBack || _messageModel.messageType == ChatMessageTypeCallBack2) { _bubbleImageView.hidden = YES; return; } _bubbleImageView.hidden = NO; UIImage *bubbleImage = _messageModel.isSender ? [UIImage imageNamed:@"rightlt"] : [UIImage imageNamed:@"leftlt"]; // 拉伸气泡图片 UIEdgeInsets insets = UIEdgeInsetsMake(30, _messageModel.isSender ? 20 : 10, 10, _messageModel.isSender ? 10 : 20); bubbleImage = [bubbleImage resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch]; _bubbleImageView.image = bubbleImage; } - (void)setupLongPressEvent{ if (_isHistory) { return; } if ( _messageModel.messageType == ChatMessageTypeEvent || _messageModel.messageType == ChatMessageTypeCallBack || _messageModel.messageType == ChatMessageTypeCall || _messageModel.messageType == ChatMessageTypeCallBack2) { return; } // NSLog(@"setupLongPressEvent-------1"); // 创建长按手势识别器并设置目标方法 UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressEvent:)]; longPress.minimumPressDuration = 0.3; // 设置长按的最小时间为0.3秒 [self.currentContentView addGestureRecognizer:longPress]; // 将手势识别器添加到视图上 if(_messageModel.messageType ==ChatMessageTypeText){ [self.bubbleImageView addGestureRecognizer:longPress]; // 将手势识别器添加到视图上 } else{ [self.currentContentView addGestureRecognizer:longPress]; // 将手势识别器添加到视图上 } } - (void)longPressEvent:(UILongPressGestureRecognizer *)gestureRecognizer { if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { NSLog(@"长按事件开始"); // 在这里添加你的代码,例如显示菜单、编辑选项等 if (_messageModel.messageType == ChatMessageTypeDel || _messageModel.messageType == ChatMessageTypeCallBack) { return; } weakSelf(self); if(weakself.bubbleCellLongPressMenuBlock){ weakself.bubbleCellLongPressMenuBlock(99); } BOOL isSender = NO; if (_messageModel.isSender) { isSender = YES; } ChatLongPressMenuView * contenView = [[ChatLongPressMenuView alloc] initWithMessageType:_messageModel.messageType isSender:isSender]; BOOL isMedia = NO; if (_messageModel.messageType == ChatMessageTypeImage || _messageModel.messageType == ChatMessageTypeVideo) { isMedia = YES; }else{ isMedia = NO; } ChatPopoverView * popover = [[ChatPopoverView alloc] initWithContentView:contenView sourceView:isMedia ? self.currentContentView : self.bubbleImageView]; [popover show]; contenView.indexOfMenuClicked = ^(NSInteger index) { [popover dismiss]; !weakself.bubbleCellLongPressMenuBlock ?: weakself.bubbleCellLongPressMenuBlock(index); }; } else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) { NSLog(@"长按事件结束"); } NSLog(@"longPressEvent-------"); } - (void)batchStateButtonClicked{ self.batchStateBtn.selected = !self.batchStateBtn.isSelected; !self.batchSelectedStateBlock ?: self.batchSelectedStateBlock(self.batchStateBtn.isSelected); } #pragma mark - 内容配置具体实现 - (void)setupTextContent { _textLbl.hidden = NO; if (_messageModel.quoteMessage) { self.quoteLbl.hidden = NO; self.quoteLbl.text = [NSString stringWithFormat:@"%@:%@",_messageModel.quoteMessage.nickName,_messageModel.quoteMessage.content]; }else{ _quoteLbl.hidden = YES; } // NSLog(@"_messageModel.content:%@",_messageModel.content); // 创建富文本 NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:_messageModel.content ?: @""]; // 处理特殊内容(链接、@用户等) [self highlightSpecialElementsInText:text]; // 同步设置文本,避免闪烁 // [UIView performWithoutAnimation:^{ // _textLbl.attributedText = text; // }]; if(_matchesArray.count>0){ NSMutableArray *array = [NSMutableArray new]; for (NSTextCheckingResult *match in _matchesArray) { if (match.resultType == NSTextCheckingTypeLink) { NSString *url =[NSString stringWithFormat:@"%@",match.URL]; url=[url stringByReplacingOccurrencesOfString:@"http://" withString:@""]; url=[url stringByReplacingOccurrencesOfString:@"https://" withString:@""]; [array addObject:url]; } } _textLbl.attributedText = [self getAttributeWith:array string:_messageModel.content orginFont:18 orginColor:[UIColor darkGrayColor] attributeFont:18 attributeColor:[UIColor blueColor]]; // _textLbl.attributedText = text; [_textLbl yb_addAttributeTapActionWithStrings:array tapClicked:^(UILabel *label, NSString *string, NSRange range, NSInteger index) { NSLog(@"%ld:%@",(long)index,self->_matchesArray[index]); NSTextCheckingResult *match=self->_matchesArray[index]; // [[UIApplication sharedApplication] openURL:match.URL options:@{} completionHandler:nil]; // 点击后的操作 SFSafariViewController *svc = [[SFSafariViewController alloc] initWithURL:match.URL]; [self.parentViewController presentViewController:svc animated:YES completion:nil]; // [FilePreviewer.shared previewFileWithLocalPath:@"" remoteURL:match.URL fromViewController:self.parentViewController]; }]; } else{ _textLbl.attributedText = text; } UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressEvent:)]; longPress.minimumPressDuration = 0.3; // 设置长按的最小时间为0.3秒 [_textLbl addGestureRecognizer:longPress]; // 将手势识别器添加到视图上 _currentContentView = _textLbl; } - (NSAttributedString *)getAttributeWith:(id)sender string:(NSString *)string orginFont:(CGFloat)orginFont orginColor:(UIColor *)orginColor attributeFont:(CGFloat)attributeFont attributeColor:(UIColor *)attributeColor { __block NSMutableAttributedString *totalStr = [[NSMutableAttributedString alloc] initWithString:string]; [totalStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:orginFont] range:NSMakeRange(0, string.length)]; [totalStr addAttribute:NSForegroundColorAttributeName value:orginColor range:NSMakeRange(0, string.length)]; NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; [paragraphStyle setLineSpacing:0.0f]; //设置行间距 [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail]; [paragraphStyle setAlignment:NSTextAlignmentLeft]; [paragraphStyle setLineBreakMode:NSLineBreakByCharWrapping]; [totalStr addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [totalStr length])]; if ([sender isKindOfClass:[NSArray class]]) { __block NSString *oringinStr = string; __weak typeof(self) weakSelf = self; [sender enumerateObjectsUsingBlock:^(NSString * _Nonnull str, NSUInteger idx, BOOL * _Nonnull stop) { NSRange range = [oringinStr rangeOfString:str]; if(range.length>0){ [totalStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:attributeFont] range:range]; [totalStr addAttribute:NSForegroundColorAttributeName value:attributeColor range:range]; // oringinStr = [oringinStr stringByReplacingCharactersInRange:range withString:[weakSelf getStringWithRange:range]]; } }]; }else if ([sender isKindOfClass:[NSString class]]) { NSRange range = [string rangeOfString:sender]; [totalStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:attributeFont] range:range]; [totalStr addAttribute:NSForegroundColorAttributeName value:attributeColor range:range]; } return totalStr; } - (NSString *)getStringWithRange:(NSRange)range { NSMutableString *string = [NSMutableString string]; for (int i = 0; i < range.length ; i++) { [string appendString:@" "]; } return string; } #pragma mark - 辅助方法 - (void)highlightSpecialElementsInText:(NSMutableAttributedString *)text { // 高亮链接 NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil]; NSArray *matches = [detector matchesInString:text.string options:0 range:NSMakeRange(0, text.length)]; NSLog(@"matches:%@",matches); _matchesArray =matches; // 高亮@用户(暂不开放) // NSRegularExpression *atRegex = [NSRegularExpression regularExpressionWithPattern:@"@[\\u4e00-\\u9fa5a-zA-Z0-9_-]+" options:0 error:nil]; // NSArray *atMatches = [atRegex matchesInString:text.string options:0 range:text.yy_rangeOfAll]; // // for (NSTextCheckingResult *match in atMatches) { // [text yy_setColor:[UIColor orangeColor] range:match.range]; // [text yy_setTextHighlightRange:match.range // color:[UIColor orangeColor] // backgroundColor:[UIColor colorWithWhite:0.9 alpha:0.5] // tapAction:^(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect) { // NSString *atUser = [text.string substringWithRange:range]; // // 处理@用户点击 // NSLog(@"点击了: %@", atUser); // }]; // } } - (void)setupImageContent { _imageContentView.hidden = NO; if (_messageModel.localurl && _messageModel.localurl.length != 0) {//显示本地图片 NSURL *localFileURL = getURL(_messageModel.localurl); // NSLog(@"UIImage------------1"); // 从 URL 获取文件路径 NSString *filePath = localFileURL.path; // 通过文件路径加载图片 UIImage *image = [UIImage imageWithContentsOfFile:filePath]; if (image) {//本地图片显示成功 // NSLog(@"UIImage------------2"); _imageContentView.image = image; CGImageRef cgImage = image.CGImage; CGFloat imageWidth = CGImageGetWidth(cgImage); CGFloat imageHeight = CGImageGetHeight(cgImage); CGFloat imageScale = imageWidth/imageHeight; CGFloat realWidth = 150 * imageScale; if(realWidth>220){ realWidth=220; imageHeight=150; } else{ imageHeight=150; } self.messageModel.mediaSize = CGSizeMake(realWidth, imageHeight); [self.imageContentView mas_updateConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(realWidth); }]; _currentContentView = _imageContentView; return; } } //没有有效本地图片 使用SDWebImage加载图片 weakSelf(self); [_imageContentView sd_setImageWithURL:[NSURL URLWithString:_messageModel.url] placeholderImage:[UIImage imageNamed:@"pictrue_placeholder"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { if (!error && image) { // 图片加载成功后可以更新布局(如果需要) // NSLog(@"UIImage------------5"); CGImageRef cgImage = image.CGImage; CGFloat imageWidth = CGImageGetWidth(cgImage); CGFloat imageHeight = CGImageGetHeight(cgImage); CGFloat imageScale = imageWidth/imageHeight; CGFloat realWidth = 150 * imageScale; if(realWidth>220){ realWidth=220; imageHeight=150; } else{ imageHeight=150; } weakself.messageModel.mediaSize = CGSizeMake(realWidth, 150); [weakself.imageContentView mas_updateConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(realWidth); }]; } }]; _currentContentView = _imageContentView; } - (void)setupVideoContent { _videoContentView.hidden = NO; // if (_thumbnailView.image) { // return; // } // 清除旧视图 _thumbnailView.image = nil; _loadingImg.image = nil; weakSelf(self); if (_messageModel.videoThumbnailImage) { // NSLog(@"videoThumbnailImage----:%@",_messageModel.videoThumbnailImage); _thumbnailView.image = _messageModel.videoThumbnailImage; [_thumbnailView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(_videoContentView); }]; }else{ // NSLog(@"videoThumbnailImage----load"); [_messageModel generateThumbnailWithCompletion:^(UIImage * _Nonnull thumbnail) { // NSLog(@"111111---------------"); if (thumbnail) { // NSLog(@"2222---------------"); dispatch_async(dispatch_get_main_queue(), ^{ [weakself.thumbnailView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self->_videoContentView); }]; weakself.thumbnailView.image = thumbnail; }); } }]; } _messageModel.uploadPersentChange = ^(NSInteger index,NSInteger localtime) { if(localtime==weakself.messageModel.localtime){ if(index>=100){ dispatch_async(dispatch_get_main_queue(), ^{ if(weakself.jinduLbl){ weakself.jinduLbl.alpha = 0; weakself.loadingImg.alpha = 0; } }); } else{ dispatch_async(dispatch_get_main_queue(), ^{ if(weakself.jinduLbl){ if(weakself.loadingImg.alpha!=1||!weakself.loadingImg.image){ NSString *gifPath = [[NSBundle mainBundle] pathForResource:@"load" ofType:@"gif"]; NSData *gifData = [NSData dataWithContentsOfFile:gifPath]; UIImage *placeholder = [UIImage sd_imageWithGIFData:gifData]; weakself.loadingImg.image = placeholder; weakself.loadingImg.alpha = 1; } weakself.jinduLbl.alpha = 1; weakself.jinduLbl.text = [NSString stringWithFormat:@"%ld",(long)index]; } }); } } else{ dispatch_async(dispatch_get_main_queue(), ^{ if(weakself.jinduLbl){ weakself.jinduLbl.alpha = 0; weakself.loadingImg.alpha = 0; } }); } }; _currentContentView = _videoContentView; } - (void)setupFileContent { _fileContentView.hidden = NO; // 获取子视图 UIImageView *iconView = _fileContentView.subviews[0]; UILabel *nameLabel = _fileContentView.subviews[1]; UILabel *sizeLabel = _fileContentView.subviews[2]; // 设置文件内容 iconView.image = [UIImage imageNamed: @"file_icon_black"]; nameLabel.text = _messageModel.fileName ?: @"未知文件"; sizeLabel.text = _messageModel.customFileSize ?: @"0KB"; _currentContentView = _fileContentView; } - (void)setupVoiceContent { _voiceContentView.hidden = NO; // 获取子视图 UIImageView *voiceIcon = _voiceContentView.subviews[0]; UILabel *durationLabel = _voiceContentView.subviews[1]; voiceIcon.animationDuration = 1.f; voiceIcon.animationRepeatCount = 0; // 设置语音内容 voiceIcon.image = [UIImage imageNamed:_messageModel.isSender ? @"yuyinR" : @"yuyinL"]; durationLabel.text = [NSString stringWithFormat:@"%ld\"", (long)_messageModel.voiceDuration]; // 根据发送方调整布局 if (_messageModel.isSender) { [voiceIcon mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_voiceContentView).offset(-8); make.centerY.equalTo(_voiceContentView); make.width.height.mas_equalTo(20); }]; [durationLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(voiceIcon.mas_left).offset(-8); make.centerY.equalTo(_voiceContentView); }]; voiceIcon.animationImages = @[kImageMake(@"audio_sender_animate1"),kImageMake(@"audio_sender_animate2"),kImageMake(@"audio_sender_animate3")]; } else { [voiceIcon mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_voiceContentView).offset(8); make.centerY.equalTo(_voiceContentView); make.width.height.mas_equalTo(20); }]; [durationLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(voiceIcon.mas_right).offset(8); make.centerY.equalTo(_voiceContentView); }]; voiceIcon.animationImages = @[kImageMake(@"audio_receiver_animate1"),kImageMake(@"audio_receiver_animate2"),kImageMake(@"audio_receiver_animate3")]; } _currentContentView = _voiceContentView; } - (void)setupCallContent { _callContentView.hidden = NO; // 获取子视图 UIImageView *callIcon = _callContentView.subviews[0]; UILabel *callLabel = _callContentView.subviews[1]; UILabel *durationLabel = _callContentView.subviews[2]; // 设置通话记录内容 callIcon.image = [UIImage imageNamed:_messageModel.isVideo ? @"vedio_call_icon" : @"audio_call_icon"]; callLabel.text = _messageModel.isCallSuccess ? @"通话时长" : @"未接通"; durationLabel.text = _messageModel.isCallSuccess ? [self formatTimeWithMilliseconds:_messageModel.callDuration] : @""; _currentContentView = _callContentView; } - (void)setupHistoryContent { _historyContentView.hidden = NO; // 获取子视图 UILabel *titleLabel = _historyContentView.subviews[0]; UILabel *contentLabel = (UILabel *)_historyContentView.subviews[1]; // 设置聊天记录内容 titleLabel.text = @"聊天记录"; NSArray * msgArray = _messageModel.forwardMsgArray; NSMutableArray * contentArray = [NSMutableArray array]; for (NSDictionary * dict in msgArray) { NSString * messageType = dict[@"messageType"]; NSString * nickName = dict[@"fromName"]?:@""; NSString * recordString = @""; switch ([messageType integerValue]) { case 0: // recordString = [NSString stringWithFormat:@"%@:%@\n",nickName,dict[@"content"]?:@""]; recordString = [NSString stringWithFormat:@"%@:%@\n",nickName,[CryptoAES.shareInstance decryptDataL:dict[@"content"]]]; break; case 1: recordString = [NSString stringWithFormat:@"%@:【图片】\n",nickName];; break; case 2: recordString = [NSString stringWithFormat:@"%@:【文件】\n",nickName]; break; case 3: recordString = [NSString stringWithFormat:@"%@:【语音】\n",nickName]; break; case 5: recordString = [NSString stringWithFormat:@"%@:【视频】\n",nickName]; break; case 6: recordString = [NSString stringWithFormat:@"%@:【转发】\n",nickName]; break; case 7: recordString = [NSString stringWithFormat:@"%@:【通话】\n",nickName]; break; default: break; } [contentArray addObject:recordString]; if (msgArray.lastObject == dict) { NSString * contentString = [contentArray componentsJoinedByString:@""]; NSMutableAttributedString *content = [[NSMutableAttributedString alloc] initWithString:contentString ?: @""]; contentLabel.attributedText = content; } } _currentContentView = _historyContentView; } - (void)setupEventContent { _eventLbl.hidden = NO; _eventLbl.text = _messageModel.content; [_eventLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(24); make.centerX.mas_equalTo(self.contentView.mas_centerX); make.centerY.mas_equalTo(self.contentView.mas_centerY); }]; [_eventLbl sizeToFit]; _currentContentView = _eventLbl; } - (void)setupCallbackContent { _callbackLbl.hidden = NO; _callbackLbl.text = _messageModel.isSender ? @"你撤回了一条消息":[NSString stringWithFormat:@"%@撤回了一条消息",_messageModel.nickName]; [_callbackLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(24); make.centerX.mas_equalTo(self.contentView.mas_centerX); make.centerY.mas_equalTo(self.contentView.mas_centerY); }]; _currentContentView = _callbackLbl; } - (void)setupDeleateContent { _deleateLbl.hidden = NO; _deleateLbl.text = @"你删除了一条消息!"; [_deleateLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(24); make.centerX.mas_equalTo(self.contentView.mas_centerX); make.centerY.mas_equalTo(self.contentView.mas_centerY); }]; _currentContentView = _deleateLbl; } #pragma mark - 布局方法 - (void)layoutContentViews { if (_messageModel.messageType == ChatMessageTypeEvent || _messageModel.messageType == ChatMessageTypeCallBack || _messageModel.messageType == ChatMessageTypeCallBack2) { return; } //复选框布局 // self.batchStateBtn.frame=CGRectMake(20, 10, 32, 32); [self.batchStateBtn mas_remakeConstraints:^(MASConstraintMaker *make) { make.size.mas_equalTo(CGSizeMake(32, 32)); make.left.mas_equalTo(20); make.top.mas_equalTo(10); }]; // 头像布局 if (_messageModel.isSender) { [_avatarImageView mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(self.contentView).offset(-kAvatarMargin); make.top.equalTo(self.contentView).offset(kVerticalMargin); make.width.height.mas_equalTo(kAvatarSize); }]; [_nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_avatarImageView.mas_left).offset(-4); make.top.equalTo(_avatarImageView); make.left.greaterThanOrEqualTo(self.contentView).offset(10); }]; [_timeLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(14); make.right.mas_equalTo(self.nameLabel.mas_left).offset(-12); make.top.mas_equalTo(self.avatarImageView); }]; if (_messageModel.messageType == ChatMessageTypeImage || _messageModel.messageType == ChatMessageTypeVideo) { [_readStateLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.mas_equalTo(self.contentView).offset(-30); make.height.mas_equalTo(14); make.top.mas_equalTo(self.currentContentView.mas_bottom).offset(2); }]; }else{ if (!_quoteLbl.hidden) { [_readStateLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.mas_equalTo(self.contentView).offset(-30); make.height.mas_equalTo(14); make.top.mas_equalTo(self.quoteLbl.mas_bottom); }]; }else{ [_readStateLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.mas_equalTo(self.contentView).offset(-30); make.height.mas_equalTo(14); make.top.mas_equalTo(self.bubbleImageView.mas_bottom); }]; } } } else { [_avatarImageView mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(self.contentView).offset(kAvatarMargin); make.top.equalTo(self.contentView).offset(kVerticalMargin); make.width.height.mas_equalTo(kAvatarSize); }]; [_nameLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_avatarImageView.mas_right).offset(4); make.top.equalTo(_avatarImageView); make.right.lessThanOrEqualTo(self.contentView).offset(-10); }]; [_timeLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(14); make.left.mas_equalTo(self.nameLabel.mas_right).offset(12); make.top.mas_equalTo(self.avatarImageView); }]; } // 内容视图布局 [self layoutCurrentContentView]; // 气泡背景布局 if (!_bubbleImageView.hidden) { NSAssert(_currentContentView, @"currentContentView 不能为 nil"); NSAssert(_currentContentView.superview, @"currentContentView 必须已添加到父视图"); [_bubbleImageView mas_remakeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(_currentContentView).insets(UIEdgeInsetsMake(-kTextVerticalMargin, -kTextHorizontalMargin, -kTextVerticalMargin, -kTextHorizontalMargin)); }]; [self.contentView sendSubviewToBack:_bubbleImageView]; } } - (void)layoutCurrentContentView { switch (_messageModel.messageType) { case ChatMessageTypeText: [self layoutTextContent]; break; case ChatMessageTypeImage: [self layoutImageContent]; break; case ChatMessageTypeVideo: [self layoutVideoContent]; break; case ChatMessageTypeVoice: [self layoutVioceContent]; break; case ChatMessageTypeCall: [self layoutCallContent]; break; case ChatMessageTypeHistory: [self layoutHistoryContent]; break; default: [self layoutOtherContent]; break; } } - (void)layoutTextContent { CGSize textSize = [self calculateTextSize]; if (_messageModel.isSender) { [_textLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_avatarImageView.mas_left).offset(-26); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_equalTo(textSize.width); make.height.mas_equalTo(textSize.height); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; if (_messageModel.quoteMessage) { [_quoteLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(20); make.top.mas_equalTo(_textLbl.mas_bottom).offset(20); make.right.mas_equalTo(_textLbl.mas_right).offset(6); make.width.mas_lessThanOrEqualTo(kContentMaxWidth); }]; } } else { [_textLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_avatarImageView.mas_right).offset(26); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_equalTo(textSize.width); make.height.mas_equalTo(textSize.height); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; if (_messageModel.quoteMessage) { [_quoteLbl mas_remakeConstraints:^(MASConstraintMaker *make) { make.height.mas_equalTo(20); make.top.mas_equalTo(_textLbl.mas_bottom).offset(20); make.left.mas_equalTo(_textLbl.mas_left).offset(-6); make.width.mas_lessThanOrEqualTo(kContentMaxWidth); }]; } } } - (void)layoutImageContent { // CGSize imageSize = [self adjustMediaSize:_messageModel.mediaSize]; CGSize imageSize = _messageModel.mediaSize; if (_messageModel.isSender) { [_imageContentView mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_avatarImageView.mas_left).offset(-16); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_equalTo(imageSize.width); make.height.mas_equalTo(imageSize.height); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } else { [_imageContentView mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_avatarImageView.mas_right).offset(16); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_equalTo(imageSize.width); make.height.mas_equalTo(imageSize.height); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } } - (void)layoutVideoContent { CGSize videoSize = [self adjustMediaSize:_messageModel.mediaSize]; if (_messageModel.isSender) { [_videoContentView mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_avatarImageView.mas_left).offset(-16); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_equalTo(videoSize.width); make.height.mas_equalTo(videoSize.height); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } else { [_videoContentView mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_avatarImageView.mas_right).offset(16); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_equalTo(videoSize.width); make.height.mas_equalTo(videoSize.height); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } } - (void)layoutVioceContent { // 设置内容视图约束 [_voiceContentView mas_remakeConstraints:^(MASConstraintMaker *make) { if (_messageModel.isSender) { make.right.equalTo(_avatarImageView.mas_left).offset(-26); } else { make.left.equalTo(_avatarImageView.mas_right).offset(26); } make.top.equalTo(_avatarImageView).offset(28); make.width.mas_equalTo(_messageModel.voiceWidth); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } - (void)layoutCallContent { // 获取子视图 UIImageView *callIcon = _callContentView.subviews[0]; UILabel *callLabel = _callContentView.subviews[1]; UILabel *durationLabel = _callContentView.subviews[2]; if (_messageModel.isSender) { [_callContentView mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_avatarImageView.mas_left).offset(-26); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_lessThanOrEqualTo(kContentMaxWidth); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } else { [_callContentView mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_avatarImageView.mas_right).offset(26); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_lessThanOrEqualTo(kContentMaxWidth); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } if (_messageModel.isCallSuccess) { [callLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(callIcon.mas_right).offset(8); make.top.equalTo(_callContentView).offset(8); }]; [durationLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(callLabel.mas_right).offset(8); make.top.equalTo(callLabel); make.right.equalTo(_callContentView).offset(-8); make.bottom.equalTo(_callContentView).offset(-8); }]; }else{ [callLabel mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(callIcon.mas_right).offset(8); make.top.equalTo(_callContentView).offset(8); make.width.mas_lessThanOrEqualTo(50); make.right.equalTo(_callContentView).offset(-8); }]; } } - (void)layoutHistoryContent { [_historyContentView mas_remakeConstraints:^(MASConstraintMaker *make) { if (_messageModel.isSender) { make.right.equalTo(_avatarImageView.mas_left).offset(-26); } else { make.left.equalTo(_avatarImageView.mas_right).offset(26); } make.top.equalTo(_avatarImageView).offset(28); make.width.mas_equalTo(160); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } - (void)layoutOtherContent { if (_messageModel.isSender) { [_currentContentView mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_avatarImageView.mas_left).offset(-26); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_lessThanOrEqualTo(kContentMaxWidth); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } else { [_currentContentView mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(_avatarImageView.mas_right).offset(26); make.top.mas_equalTo(_avatarImageView.mas_top).offset(28); make.width.mas_lessThanOrEqualTo(kContentMaxWidth); make.bottom.equalTo(self.contentView).offset(-kVerticalMargin).priorityHigh(); }]; } } - (CGSize)adjustMediaSize:(CGSize)originalSize { CGFloat maxWidth = kContentMaxWidth; CGFloat maxHeight = 200.0f; if (originalSize.width <= 0 || originalSize.height <= 0) { return CGSizeMake(maxWidth, maxWidth * 0.6); } CGFloat ratio = originalSize.width / originalSize.height; CGFloat adjustedWidth = maxWidth; CGFloat adjustedHeight = adjustedWidth / ratio; if (adjustedHeight > maxHeight) { adjustedHeight = maxHeight; adjustedWidth = adjustedHeight * ratio; } return CGSizeMake(adjustedWidth, adjustedHeight); } + (CGSize)otherAdjustMediaSize:(CGSize)originalSize { CGFloat maxWidth = kContentMaxWidth; CGFloat maxHeight = 200.0f; if (originalSize.width <= 0 || originalSize.height <= 0) { return CGSizeMake(maxWidth, maxWidth * 0.6); } CGFloat ratio = originalSize.width / originalSize.height; CGFloat adjustedWidth = maxWidth; CGFloat adjustedHeight = adjustedWidth / ratio; if (adjustedHeight > maxHeight) { adjustedHeight = maxHeight; adjustedWidth = adjustedHeight * ratio; } return CGSizeMake(adjustedWidth, adjustedHeight); } - (CGSize)calculateTextSize{ NSString *text = _messageModel.content ?: @""; UILabel *label = [[UILabel alloc] init]; label.font = [UIFont systemFontOfSize:18.0]; // 设置字体大小 label.numberOfLines = 0; // 设置为0表示不限制行数 label.text = text; // 设置文本内容 label.preferredMaxLayoutWidth = kContentMaxWidth; // 设置固定宽度 CGSize size = [label sizeThatFits:CGSizeMake(kContentMaxWidth, CGFLOAT_MAX)]; CGSize textSize = size; // NSLog(@"文本宽度: %f, 文本高度: %f", textSize.width, textSize.height); return textSize; } - (NSString *)formatTimeWithMilliseconds:(NSInteger)milliseconds{ NSInteger seconds = milliseconds / 1000.0; if (seconds >= 60) { // 大于等于60秒时显示为分钟,保留一位小数 NSInteger minutes = seconds / 60.0; NSInteger sc = seconds%60; return [NSString stringWithFormat:@"%ld分%ld秒", (long)minutes,(long)sc]; } else { // 小于60秒时显示为秒,保留整数 return [NSString stringWithFormat:@"%ld秒", seconds]; } } #pragma mark 列表事件处理 - (void)handleImageTap { if (_messageModel.messageType == ChatMessageTypeImage) { // 处理图片点击 NSLog(@"点击了图片: %@", _messageModel.url); if(_messageModel.fileError==1){ weakSelf(self); if(weakself.bubbleCellLongPressMenuBlock){ weakself.bubbleCellLongPressMenuBlock(8); } return; } if (_messageModel.url.length == 0 && !_messageModel.isSender) { [MBProgressHUD showWithText:@"图片正在上传"]; return; } [FilePreviewer.shared previewFileWithLocalPath:_messageModel.localurl remoteURL:getURL(_messageModel.url) fromViewController:self.parentViewController]; } } - (void)handleVideoTap { if (_messageModel.messageType == ChatMessageTypeVideo) { // 处理视频点击 NSLog(@"点击了视频: %@", _messageModel.url); if(_messageModel.fileError==1){ weakSelf(self); if(weakself.bubbleCellLongPressMenuBlock){ weakself.bubbleCellLongPressMenuBlock(8); } return; } if (_messageModel.url.length == 0 && !_messageModel.isSender) { [MBProgressHUD showWithText:@"视频正在上传"]; return; } weakSelf(self); [_messageModel downloadFileIfNeed:^(NSInteger persent) { if(persent>=100){ dispatch_async(dispatch_get_main_queue(), ^{ if(weakself.jinduLbl){ weakself.jinduLbl.alpha = 0; weakself.loadingImg.alpha = 0; } [FilePreviewer.shared previewFileWithLocalPath:weakself.messageModel.localurl remoteURL:getURL(weakself.messageModel.url) fromViewController:weakself.parentViewController]; }); NSLog(@"下载完成:%@",self->_messageModel.localurl); } else{ // NSLog(@"下载中:%ld",(long)persent); dispatch_async(dispatch_get_main_queue(), ^{ if(weakself.jinduLbl){ if(weakself.loadingImg.alpha!=1||!weakself.loadingImg.image){ NSString *gifPath = [[NSBundle mainBundle] pathForResource:@"load" ofType:@"gif"]; NSData *gifData = [NSData dataWithContentsOfFile:gifPath]; UIImage *placeholder = [UIImage sd_imageWithGIFData:gifData]; weakself.loadingImg.image = placeholder; weakself.loadingImg.alpha = 1; } weakself.jinduLbl.alpha = 1; weakself.jinduLbl.text = [NSString stringWithFormat:@"%ld",(long)persent]; } }); } }]; } } - (void)handleFileTap { if (_messageModel.messageType == ChatMessageTypeFile) { // 处理视频点击 NSLog(@"点击了文件: %@", _messageModel.url); if(_messageModel.fileError==1){ weakSelf(self); if(weakself.bubbleCellLongPressMenuBlock){ weakself.bubbleCellLongPressMenuBlock(8); } return; } if (_messageModel.url.length == 0 && !_messageModel.isSender) { [MBProgressHUD showWithText:@"文件正在上传"]; return; } UILabel *sizeLabel = _fileContentView.subviews[2]; weakSelf(self) [_messageModel downloadFileIfNeed:^(NSInteger persent) { dispatch_async(dispatch_get_main_queue(), ^{ sizeLabel.text = [NSString stringWithFormat:@"%ld",(long)persent]; if(persent>=100){ sizeLabel.text = weakself.messageModel.customFileSize; NSLog(@"setupFileContent:下载完成:%@",weakself.messageModel.localurl); [FilePreviewer.shared previewFileWithLocalPath:weakself.messageModel.localurl remoteURL:getURL(weakself.messageModel.url) fromViewController:weakself.parentViewController]; } }); }]; } } - (void)handleVoiceTap { if (_messageModel.messageType == ChatMessageTypeVoice) { // 处理语音点击 NSLog(@"点击了语音: %@", _messageModel.url); [_messageModel downloadFileIfNeed:^(NSInteger persent) { dispatch_main_async_safe(^{ if (persent >= 100) { [self playVoice]; } }) }]; } } - (void)playVoice { UIImageView *voiceIcon = _voiceContentView.subviews[0]; if (self.player) { AVPlayerItem *playerItem = self.player.currentItem; CMTime currentTime = playerItem.currentTime; CMTime duration = playerItem.duration; [voiceIcon stopAnimating]; if (self.player.rate == 0.0) { if (CMTimeCompare(currentTime, duration) == 0 || CMTimeCompare(currentTime, duration) == 1) { // 播放完毕 NSLog(@"Playback finished"); } else { // 暂停 NSLog(@"Playback paused"); [self.player play]; return; } } else { // 正在播放 NSLog(@"Playback in progress"); [self.player pause]; return; } } NSString *urlstr = _messageModel.url; NSURL *Url = [NSURL URLWithString:urlstr]; NSLog(@"本地路径:%@",_messageModel.localurl); if (_messageModel.localurl && [[NSFileManager defaultManager] fileExistsAtPath:_messageModel.localurl]) { NSLog(@"本地路径:"); urlstr=_messageModel.localurl; Url = [NSURL fileURLWithPath:urlstr]; } else{ } NSLog(@"播放: %@", Url); AVAudioSession *audioSession = [AVAudioSession sharedInstance]; [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]; [audioSession setActive:YES error:nil]; AVPlayerItem *pitem = [AVPlayerItem playerItemWithURL:Url]; //AVPlayerItem *pitem = [[AVPlayerItem alloc] initWithURL:Url]; _player = [AVPlayer playerWithPlayerItem:pitem]; [[UIDevice currentDevice] setProximityMonitoringEnabled:YES]; //建议在播放之前设置yes,播放结束设置NO。这个功能是开启红外感应 [_player play]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:pitem]; [voiceIcon startAnimating]; } - (void)playerItemDidReachEnd:(NSNotification *)notification { // 播放结束时的处理代码 UIImageView *voiceIcon = _voiceContentView.subviews[0]; [voiceIcon stopAnimating]; NSLog(@"播放结束:%@",notification); [[UIDevice currentDevice] setProximityMonitoringEnabled:NO]; //建议在播放之前设置yes,播放结束设置NO。这个功能是开启红外感应 [[NSNotificationCenter defaultCenter] removeObserver:self name:notification.name object:notification.object]; //可以执行继续播放下一条操作 } - (void)handleHistoryTap { ChatRecordController * recordVc = [[ChatRecordController alloc] init]; for (NSDictionary * dict in _messageModel.forwardMsgArray) { [recordVc.dataArray addObject:[ChatMessageModel modelWithDictionary:dict]]; } UINavigationController * navi = [[UINavigationController alloc] initWithRootViewController:recordVc]; navi.modalPresentationStyle = UIModalPresentationFullScreen; [self.parentViewController presentViewController:navi animated:YES completion:nil]; } - (void)handleQuoteTap{ NSLog(@"handleQuoteTap----:%@",[_messageModel.quoteMessage class]); if(![_messageModel.quoteMessage isKindOfClass:[ChatMessageModel class]]){ return; } if (_messageModel.quoteMessage.messageType == ChatMessageTypeText) { QuoteDetailController * quoteDetailVc = [[QuoteDetailController alloc] init]; quoteDetailVc.quotedContent = _messageModel.quoteMessage.content; quoteDetailVc.quotedSenderName = _messageModel.quoteMessage.nickName; UINavigationController * navi = [[UINavigationController alloc] initWithRootViewController:quoteDetailVc]; navi.modalPresentationStyle = UIModalPresentationFullScreen; [self.parentViewController presentViewController:navi animated:YES completion:nil]; }else if (_messageModel.quoteMessage.messageType == ChatMessageTypeImage) { [FilePreviewer.shared previewFileWithLocalPath:_messageModel.quoteMessage.url remoteURL:getURL(_messageModel.quoteMessage.url) fromViewController:self.parentViewController]; }else if (_messageModel.quoteMessage.messageType == ChatMessageTypeVoice) { [FilePreviewer.shared previewFileWithLocalPath:_messageModel.quoteMessage.url remoteURL:getURL(_messageModel.quoteMessage.url) fromViewController:self.parentViewController]; }else if (_messageModel.quoteMessage.messageType == ChatMessageTypeFile) { [FilePreviewer.shared previewFileWithLocalPath:_messageModel.quoteMessage.url remoteURL:getURL(_messageModel.quoteMessage.url) fromViewController:self.parentViewController]; }else if (_messageModel.quoteMessage.messageType == ChatMessageTypeVoice) { } } - (void)handleAvatarTap { !self.avatarTapBlock?:self.avatarTapBlock(_messageModel); } #pragma mark - 高度计算 + (CGFloat)cellHeightForMessage:(ChatMessageModel *)message { // 如果有缓存高度,直接返回 if (message.cellHeight > 0) { return message.cellHeight; } CGFloat height = 0; CGFloat contentHeight = 0; CGFloat horizontalMargin = 12.0f; CGFloat verticalMargin = 10.0f; CGFloat maxWidth = kContentMaxWidth - 2 * horizontalMargin; // 计算内容高度 switch (message.messageType) { case ChatMessageTypeText: { NSString *text = message.content ?: @""; UIFont *font = [UIFont systemFontOfSize:17]; // 设置字体大小 CGSize maximumSize = CGSizeMake(maxWidth, CGFLOAT_MAX); // 设置最大宽度,高度设为CGFLOAT_MAX表示不限制高度 NSDictionary *attributes = @{NSFontAttributeName: font}; CGRect rect = [text boundingRectWithSize:maximumSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; CGFloat height = rect.size.height; contentHeight = height + 2 * verticalMargin; contentHeight = MAX(contentHeight, kMinBubbleHeight); if (message.quoteMessage) { contentHeight = contentHeight + 30; } break; } case ChatMessageTypeImage: case ChatMessageTypeVideo: { CGSize mediaSize = [self otherAdjustMediaSize:message.mediaSize]; contentHeight = mediaSize.height; break; } case ChatMessageTypeFile: contentHeight = 60.0f; break; case ChatMessageTypeCallBack: contentHeight = 20.f; break; case ChatMessageTypeVoice: contentHeight = 30.0f; break; case ChatMessageTypeCall: contentHeight = 40.0f; break; case ChatMessageTypeHistory: contentHeight = 80.0f; break; case ChatMessageTypeEvent: contentHeight = 20.f; break; case ChatMessageTypeCallBack2: contentHeight = 20.f; break; case ChatMessageTypeDel: contentHeight = 20.f; break; } // 总高度 = 内容高度 + 上下边距 height = contentHeight + kBubbleTopMargin+kBubbleBottomMargin; // 缓存高度 message.cellHeight = height; return height; } + (void)precalculateHeightForMessage:(ChatMessageModel *)message completion:(void(^)(CGFloat height))completion { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ CGFloat height = [self cellHeightForMessage:message]; dispatch_async(dispatch_get_main_queue(), ^{ if (completion) completion(height); }); }); } @end