前言:
上一次分享了融云的集成和使用,基本上满足了大众需求和微脉项目中的要求。但是小编私下没有偷懒,一直在探索更多的内容。融云简单的文字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];
}
}
效果图