前言:
上一次分享了融云的集成和使用,基本上满足了大众需求和微脉项目中的要求。但是小编私下没有偷懒,一直在探索更多的内容。融云简单的文字cell或则单个图片cell展示,已经满足不了我们日益强大的需求。所以这里以医生名片的自定义为例子实现一个自定义cell。
?1: ? 自定义消息 Cell包括
?1.1:在此之前我们先了解一下融云消息的发送机制和接收机制。(看图说话)
消息发送流程
消息接收流程
1.2? 自定义消息 Cell 显示需要完成两步走:
1. 自定义消息并注册消息类型
2. 自定义消息 Cell 并注册 Cell
1.2.1. 自定义消息并注册消息类型
您需要继承 RCMessageContent 实现自定义消息类,并在 SDK 初始化之后,注册自定义消息。
RCMessageContent 是消息内容类,是所有消息的基类。您可以继承此类,并实现其中的协议,来实现自定义消息。
RCMessageContent 主要有三个协议:
1. 编解码协议 RCMessageCoding
2. 存储协议 RCMessagePersistentCompatible
3. 内容摘要协议 RCMessageContentView(可选)
其中,RCMessageCoding 主要有三个功能:提供消息唯一标识符、消息发送时将消息中的所有信息编码为 json 数据传输、消息接收时将 json 数据解码还原为消息对象。
RCMessagePersistentCompatible 用于确定消息内容的存储策略,指明此消息类型在本地是否存储、是否计入未读消息数。
RCMessageContentView 用于在会话列表和本地通知中显示消息的摘要。
最后在初始化融云的时候完成注册。
[[RCIM sharedRCIM] registerMessageType:[WMRCRecommendDoctorMessage class]];
1.2.2. 自定义消息 Cell 注册并显示
如果消息不需要显示头像,请继承 RCMessageBaseCell。如果需要显示,请继承 RCMessageCell。
请在初始化方法中实现 Cell 的布局,并重写下面方法来返回 Cell 的 Size:
+ (CGSize)sizeForMessageModel:(RCMessageModel *)model
? ? ? ?withCollectionViewWidth:(CGFloat)collectionViewWidth
? ? ? ? ? referenceExtraHeight:(CGFloat)extraHeight;
然后在聊天试图页面用一下方法进行cell的注册,以及与消息的绑定。
[self registerClass:[WMRCRecommendDoctorCell class]
? ? ? ? forMessageClass:[WMRCRecommendDoctorMessage class]];
2:原理的方面都已经说完了,下面直接粗暴的上代码吧。
2.1 自定义消息体WMRCRecommendDoctorMessage(医生名片)
申明一个标志:这个标志是我们发送,接收,以及和安卓 H5互发消息时候的依据。
''#define? ? WMRCRecommendDoctorMessageTypeIdentifier @"RCD:WMRecommendDoctorMsg"
根据需求 申明需要传输的字段
/*! ?医生头像 字符串信息 ?*/
@property(nonatomic, strong) NSString *doctorHeader;
/*! 医生名字 */
?@property(nonatomic, strong) NSString *doctorName;
?/*! 医生科室 */
@property(nonatomic, strong) NSString *doctorSection;
?/*! ?预留附加信息 */
@property(nonatomic, strong) NSString *extra;
申明消息的初始话方法
/*! ?初始化测试消息
@param content 文本内容
? @return? ? ? ? 测试消息对象 */
?+ (instancetype)messageWithInquiryTextMsg:(NSString *)inquiryTextMsg withInquiryPicture:(NSMutableArray *)inquiryPictureArr;
在.m中实现以下方法
///初始化
?+ (instancetype)messageWithDoctorHeader:(NSString *)doctorHeader withDoctorName:(NSString *)doctorName withDoctorSection:(NSString *)doctorSection{
? ? WMRCRecommendDoctorMessage *text = [[WMRCRecommendDoctorMessage alloc] init];
? ? ?if (text) {
? ? ? ?text.doctorHeader = doctorHeader;
? ? ? ? text.doctorName=doctorName;
? ? ? ?text.doctorSection=doctorSection;
? ?}
? ? ?return text;
}
消息是否存储,是否计入未读数
?+ (RCMessagePersistent)persistentFlag {
? ? return (MessagePersistent_ISPERSISTED | MessagePersistent_ISCOUNTED);
}
NSCoding(反序列化)
?- (instancetype)initWithCoder:(NSCoder *)aDecoder {
? ? self = [super init];
? ? ?if (self) {
? ? ? ? ?self.doctorHeader = [aDecoder decodeObjectForKey:@"doctorHeader"];
? ? ? ? ?self.extra = [aDecoder decodeObjectForKey:@"extra"];
? ? ? ? ?self.doctorName = [aDecoder decodeObjectForKey:@"doctorName"];
? ? ? ? ?self.doctorSection = [aDecoder decodeObjectForKey:@"doctorSection"];
? ? ?}
? ? ?return self;
?}
NSCoding 序列化
?- (void)encodeWithCoder:(NSCoder *)aCoder {
? ? ?[aCoder encodeObject:self.doctorSection forKey:@"doctorSection"];
? ? ?[aCoder encodeObject:self.extra forKey:@"extra"];
? ? [aCoder encodeObject:self.doctorName forKey:@"doctorName"];
? ? ?[aCoder encodeObject:self.doctorHeader forKey:@"doctorHeader"];
}
将消息内容编码成json
?- (NSData *)encode {
? ? ?NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];
? ? ?[dataDict setObject:self.doctorHeader forKey:@"doctorHeader"];
? ? ?[dataDict setObject:self.doctorSection forKey:@"doctorSection"];
? ? ?[dataDict setObject:self.doctorName forKey:@"doctorName"];
? ? ?if (self.extra) {
? ? ? ? ?[dataDict setObject:self.extra forKey:@"extra"];
? ? ?}
? ? ?if (self.senderUserInfo) {
? ? ? ? ?NSMutableDictionary *userInfoDic = [[NSMutableDictionary alloc] init];
? ? ? ? ?if (self.senderUserInfo.name) {
? ? ? ? ? ? ?[userInfoDic setObject:self.senderUserInfo.name
? ? ? ? ? ? ? ? ? forKeyedSubscript:@"name"];
? ? ? ? ?}
? ? ? ? ?if (self.senderUserInfo.portraitUri) {
? ? ? ? ? ? ?[userInfoDic setObject:self.senderUserInfo.portraitUri
? ? ? ? ? ? ? ? ? forKeyedSubscript:@"icon"];
? ? ? ? ?}
? ? ? ? ?if (self.senderUserInfo.userId) {
? ? ? ? ? ? ?[userInfoDic setObject:self.senderUserInfo.userId
? ? ? ? ? ? ? ? ? forKeyedSubscript:@"id"];
? ? ? ? ?}
? ? ? ? ?[dataDict setObject:userInfoDic forKey:@"user"];
? ? ?}
? ? ?NSData *data = [NSJSONSerialization dataWithJSONObject:dataDict
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? options:kNilOptions
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? error:nil];
? ? ?return data;
?}
将json解码生成消息内容
?- (void)decodeWithData:(NSData *)data {
? ? ?if (data) {
? ? ? ? ?__autoreleasing NSError *error = nil;
? ? ? ? NSDictionary *dictionary =
? ? ? ? ?[NSJSONSerialization JSONObjectWithData:data
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?options:kNilOptions
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?error:&error];
? ? ? ? ?if (dictionary) {
? ? ? ? ? ? ?self.doctorName = dictionary[@"doctorName"];
? ? ? ? ? ? ?self.extra = dictionary[@"extra"];
? ? ? ? ? ? ?self.doctorSection = dictionary[@"doctorSection"];
? ? ? ? ? ? ?self.doctorHeader = dictionary[@"doctorHeader"];
? ? ? ? ? ? ?NSDictionary *userinfoDic = dictionary[@"user"];
? ? ? ? ? ? ?[self decodeUserInfo:userinfoDic];
? ? ? ? ?}
? ? ?}
?}
会话列表中显示的摘要
?- (NSString *)conversationDigest {
? ? ?return self.doctorName;
?}
消息的类型名
?+ (NSString *)getObjectName {
? ? ?return WMRCRecommendDoctorMessageTypeIdentifier;
?}
?2.2 自定义消息体WMRCInquiryMessageCell
根据需求申明变量
/*! 医生头像 */
?@property(strong, nonatomic) UIImageView *headerImageView;
?/*! 医生姓名 */
?@property(strong, nonatomic) UILabel *nameLable;
?/*! 医生科室 */
?@property(strong, nonatomic)UILabel *sectionLable;
?/*! 背景View */
?@property(nonatomic, strong) UIImageView *bubbleBackgroundView;
在.m中声明一些全局变量 方便日后容易改动
#define kheight 90? ? //名片的高度
?#define kwidth? 220? //名片的宽度
?#define kSpacing 7? ? //个控件之间的间隔
?#define kBubbleSharp 10 //气泡尖尖的宽度
?#define kheaderHeight 60 //头像的宽高
当应用自定义消息时,必须实现该方法来返回cell的Size。
其中,extraHeight是Cell根据界面上下文,需要额外显示的高度(比如时间、用户名的高度等)。
一般而言,Cell的高度应该是内容显示的高度再加上extraHeight的高度。
+ (CGSize)sizeForMessageModel:(RCMessageModel *)model
? ?withCollectionViewWidth:(CGFloat)collectionViewWidth
?referenceExtraHeight:(CGFloat)extraHeight {
//由于名片的高度,宽度一定 所以 下面这句代码是没有用途的
?WMRCRecommendDoctorMessage *message = (WMRCRecommendDoctorMessage *)model.content;
?? ?//由于名片的高度,宽度一定 所以可以直接返回固定高度 50 是从文档那看到的之和10+20+10+10
?return CGSizeMake(kScreen_width, kheight+55);
?}
cell 的初始化方法
- (instancetype)initWithFrame:(CGRect)frame {
?self = [super initWithFrame:frame];
if (self) {
?[self initialize];
?}?
return self;
?}
?- (id)initWithCoder:(NSCoder *)aDecoder {
?self = [super initWithCoder:aDecoder];
if (self) {
?[self initialize];
?}
return self;
?}
创建cell显示的控件
- (void)initialize {
? ? self.bubbleBackgroundView = [[UIImageView alloc] initWithFrame:CGRectZero];
? ? ?//[self.messageContentView 是底层的View
? ? ?[self.messageContentView addSubview:self.bubbleBackgroundView];
? ? ?self.messageContentView.backgroundColor=[UIColor greenColor];
? ? //头像
? ? self.headerImageView=[[UIImageView alloc]init];
? ? ?[self.bubbleBackgroundView addSubview:self.headerImageView];
? ? //名字
? ? ?self.nameLable=[[RCAttributedLabel alloc] initWithFrame:CGRectZero];
? ? ?[self.nameLable setFont:[UIFont systemFontOfSize:Test_Message_Font_Size]];
? ? ?self.nameLable.numberOfLines = 0;
? ? ?[self.nameLable setLineBreakMode:NSLineBreakByWordWrapping];
? ? ?[self.nameLable setTextAlignment:NSTextAlignmentLeft];
? ? ?[self.nameLable setTextColor:[UIColor blackColor]];
? ? ?[self.bubbleBackgroundView addSubview:self.nameLable];
? ? ?//科室
? ? ?self.sectionLable = [[RCAttributedLabel alloc] initWithFrame:CGRectZero];
? ? ?[self.sectionLable setFont:[UIFont systemFontOfSize:Test_Message_Font_Size]];
? ? ?self.sectionLable.numberOfLines = 0;
? ? ?[self.sectionLable setLineBreakMode:NSLineBreakByWordWrapping];
? ? ?[self.sectionLable setTextAlignment:NSTextAlignmentLeft];
? ? ?[self.sectionLable setTextColor:[UIColor blackColor]];
? ? ?[self.bubbleBackgroundView addSubview:self.sectionLable];
? ? ?self.bubbleBackgroundView.userInteractionEnabled = YES;
? ? ?UILongPressGestureRecognizer *longPress =
? ? ?[[UILongPressGestureRecognizer alloc]
? ? ? initWithTarget:self
? ? ? action:@selector(longPressed:)];
? ? ?[self.bubbleBackgroundView addGestureRecognizer:longPress];
? ? ?UITapGestureRecognizer *textMessageTap = [[UITapGestureRecognizer alloc]
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?initWithTarget:self
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?action:@selector(tapTextMessage:)]; ? ? textMessageTap.numberOfTapsRequired = 1;
? ? ?textMessageTap.numberOfTouchesRequired = 1;
? ? ?//[self.textLabel addGestureRecognizer:textMessageTap];
? ? ?//self.textLabel.userInteractionEnabled = YES;
?}
set方法获取数据
- (void)setDataModel:(RCMessageModel *)model {
?[super setDataModel:model];
?[self setAutoLayout];
}
根据model对控件在cell上布局,其中分为接收方和发送方
?-(void)setAutoLayout {
?//创建控件
?WMRCRecommendDoctorMessage *testMessage = (WMRCRecommendDoctorMessage *)self.model.content;
?if (testMessage) {
?self.nameLable.text = testMessage.doctorName;
?[self.headerImageView sd_setImageWithURL:[NSURL URLWithString:testMessage.doctorHeader]];
?self.sectionLable.text=testMessage.doctorSection;
?}
?//获取文字消息的size
?//CGSize textLabelSize = [[self class] getTextLabelSize:testMessage];
?//大背景的size(单纯的按照 textLabelSize 是不对的)
?CGSize bubbleBackgroundViewSize =CGSizeMake(kwidth, kheight);
?//消息内容的View
?CGRect messageContentViewRect = self.messageContentView.frame;
?//拉伸图片
?if (MessageDirection_RECEIVE == self.messageDirection) {//接受
?self.headerImageView.frame=CGRectMake(2*kSpacing, kSpacing, kheaderHeight, kheaderHeight);
?//文字的位置
?self.nameLable.frame =
CGRectMake(14+kheaderHeight+kSpacing, kSpacing, kwidth-kheaderHeight-kSpacing-kBubbleSharp, kBubbleSharp*2);
?//图片的位置 self.sectionLable.frame=CGRectMake(self.nameLable.frame.origin.x, self.nameLable.frame.origin.y+self.nameLable.frame.size.height,kwidth-kheaderHeight-kSpacing-kBubbleSharp, kheight-40);
?//消息内容的View的宽 赋值
?messageContentViewRect.size.width = bubbleBackgroundViewSize.width;
?messageContentViewRect.size.height = bubbleBackgroundViewSize.height;
?//大泡泡背景 赋值
?self.messageContentView.frame = messageContentViewRect;
self.bubbleBackgroundView.frame = CGRectMake(
?0, 0, bubbleBackgroundViewSize.width, bubbleBackgroundViewSize.height);
?UIImage *image = [RCKitUtility imageNamed:@"chat_from_bg_normal"
?ofBundle:@"RongCloud.bundle"];
self.bubbleBackgroundView.image = [image
? ? resizableImageWithCapInsets:UIEdgeInsetsMake(image.size.height * 0.8,
image.size.width * 0.8,
image.size.height * 0.2,
image.size.width * 0.2)];
?} else {
?self.headerImageView.frame=CGRectMake(kSpacing, kSpacing, kheaderHeight, kheaderHeight);
?//自己发消息
?self.nameLable.frame =
?CGRectMake(kSpacing+kheaderHeight+kSpacing, kSpacing, kwidth-kheaderHeight-2*kSpacing-2*kBubbleSharp, 2*kBubbleSharp);
?//图片的位置
self.sectionLable.frame=CGRectMake(self.nameLable.frame.origin.x, self.nameLable.frame.origin.y+self.nameLable.frame.size.height, kwidth-kheaderHeight-14-2*kBubbleSharp, kheight-40);
?//消息内容的View的宽 赋值
?messageContentViewRect.size.width = bubbleBackgroundViewSize.width;
?messageContentViewRect.size.height = bubbleBackgroundViewSize.height;
?messageContentViewRect.origin.x =
?self.baseContentView.bounds.size.width -
?(messageContentViewRect.size.width + HeadAndContentSpacing +
?[RCIM sharedRCIM].globalMessagePortraitSize.width + kBubbleSharp);
?self.messageContentView.frame = messageContentViewRect;
?self.bubbleBackgroundView.frame = CGRectMake(
0, 0, bubbleBackgroundViewSize.width, bubbleBackgroundViewSize.height);
?UIImage *image = [RCKitUtility imageNamed:@"chat_to_bg_normal"
ofBundle:@"RongCloud.bundle"];
?self.bubbleBackgroundView.image = [image
resizableImageWithCapInsets:UIEdgeInsetsMake(image.size.height * 0.8,
image.size.width * 0.2,
image.size.height * 0.2,
image.size.width * 0.8)];
?}
?}
点击问题部分的手势,利用这个在视图页面进行交互
- (void)tapTextMessage:(UIGestureRecognizer *)gestureRecognizer {
? ? ?if ([self.delegate respondsToSelector:@selector(didTapMessageCell:)]) {
? ? ? ? ?[self.delegate didTapMessageCell:self.model];
? ? ?}
?}
长按手势,利用这个在视图页面进行交互
- (void)longPressed:(id)sender {
? ? ?UILongPressGestureRecognizer *press = (UILongPressGestureRecognizer *)sender;
? ? ?if (press.state == UIGestureRecognizerStateEnded) {
? ? ? ? ?return;
? ? ?} else if (press.state == UIGestureRecognizerStateBegan) {
? ? ? ? ?[self.delegate didLongTouchMessageCell:self.model
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?inView:self.bubbleBackgroundView];
? ? ?}
?}
效果图