前言:
上一次分享了融云的集成和使用,基本上滿足了大眾需求和微脈項(xiàng)目中的要求。但是小編私下沒(méi)有偷懶,一直在探索更多的內(nèi)容。融云簡(jiǎn)單的文字cell或則單個(gè)圖片cell展示,已經(jīng)滿足不了我們?nèi)找鎻?qiáng)大的需求。所以這里以醫(yī)生名片的自定義為例子實(shí)現(xiàn)一個(gè)自定義cell。
?1: ? 自定義消息 Cell包括
?1.1:在此之前我們先了解一下融云消息的發(fā)送機(jī)制和接收機(jī)制。(看圖說(shuō)話)

消息發(fā)送流程

消息接收流程
1.2? 自定義消息 Cell 顯示需要完成兩步走:
1. 自定義消息并注冊(cè)消息類型
2. 自定義消息 Cell 并注冊(cè) Cell
1.2.1. 自定義消息并注冊(cè)消息類型
您需要繼承 RCMessageContent 實(shí)現(xiàn)自定義消息類,并在 SDK 初始化之后,注冊(cè)自定義消息。
RCMessageContent 是消息內(nèi)容類,是所有消息的基類。您可以繼承此類,并實(shí)現(xiàn)其中的協(xié)議,來(lái)實(shí)現(xiàn)自定義消息。
RCMessageContent 主要有三個(gè)協(xié)議:
1. 編解碼協(xié)議 RCMessageCoding
2. 存儲(chǔ)協(xié)議 RCMessagePersistentCompatible
3. 內(nèi)容摘要協(xié)議 RCMessageContentView(可選)
其中,RCMessageCoding 主要有三個(gè)功能:提供消息唯一標(biāo)識(shí)符、消息發(fā)送時(shí)將消息中的所有信息編碼為 json 數(shù)據(jù)傳輸、消息接收時(shí)將 json 數(shù)據(jù)解碼還原為消息對(duì)象。
RCMessagePersistentCompatible 用于確定消息內(nèi)容的存儲(chǔ)策略,指明此消息類型在本地是否存儲(chǔ)、是否計(jì)入未讀消息數(shù)。
RCMessageContentView 用于在會(huì)話列表和本地通知中顯示消息的摘要。
最后在初始化融云的時(shí)候完成注冊(cè)。
[[RCIM sharedRCIM] registerMessageType:[WMRCRecommendDoctorMessage class]];
1.2.2. 自定義消息 Cell 注冊(cè)并顯示
如果消息不需要顯示頭像,請(qǐng)繼承 RCMessageBaseCell。如果需要顯示,請(qǐng)繼承 RCMessageCell。
請(qǐng)?jiān)诔跏蓟椒ㄖ袑?shí)現(xiàn) Cell 的布局,并重寫(xiě)下面方法來(lái)返回 Cell 的 Size:
+ (CGSize)sizeForMessageModel:(RCMessageModel *)model
? ? ? ?withCollectionViewWidth:(CGFloat)collectionViewWidth
? ? ? ? ? referenceExtraHeight:(CGFloat)extraHeight;
然后在聊天試圖頁(yè)面用一下方法進(jìn)行cell的注冊(cè),以及與消息的綁定。
[self registerClass:[WMRCRecommendDoctorCell class]
? ? ? ? forMessageClass:[WMRCRecommendDoctorMessage class]];
2:原理的方面都已經(jīng)說(shuō)完了,下面直接粗暴的上代碼吧。
2.1 自定義消息體WMRCRecommendDoctorMessage(醫(yī)生名片)
申明一個(gè)標(biāo)志:這個(gè)標(biāo)志是我們發(fā)送,接收,以及和安卓 H5互發(fā)消息時(shí)候的依據(jù)。
''#define? ? WMRCRecommendDoctorMessageTypeIdentifier @"RCD:WMRecommendDoctorMsg"
根據(jù)需求 申明需要傳輸?shù)淖侄?/p>
/*! ?醫(yī)生頭像 字符串信息 ?*/
@property(nonatomic, strong) NSString *doctorHeader;
/*! 醫(yī)生名字 */
?@property(nonatomic, strong) NSString *doctorName;
?/*! 醫(yī)生科室 */
@property(nonatomic, strong) NSString *doctorSection;
?/*! ?預(yù)留附加信息 */
@property(nonatomic, strong) NSString *extra;
申明消息的初始話方法
/*! ?初始化測(cè)試消息
@param content 文本內(nèi)容
? @return? ? ? ? 測(cè)試消息對(duì)象 */
?+ (instancetype)messageWithInquiryTextMsg:(NSString *)inquiryTextMsg withInquiryPicture:(NSMutableArray *)inquiryPictureArr;
在.m中實(shí)現(xiàn)以下方法
///初始化
?+ (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;
}
消息是否存儲(chǔ),是否計(jì)入未讀數(shù)
?+ (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"];
}
將消息內(nèi)容編碼成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解碼生成消息內(nèi)容
?- (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];
? ? ? ? ?}
? ? ?}
?}
會(huì)話列表中顯示的摘要
?- (NSString *)conversationDigest {
? ? ?return self.doctorName;
?}
消息的類型名
?+ (NSString *)getObjectName {
? ? ?return WMRCRecommendDoctorMessageTypeIdentifier;
?}
?2.2 自定義消息體WMRCInquiryMessageCell
根據(jù)需求申明變量
/*! 醫(yī)生頭像 */
?@property(strong, nonatomic) UIImageView *headerImageView;
?/*! 醫(yī)生姓名 */
?@property(strong, nonatomic) UILabel *nameLable;
?/*! 醫(yī)生科室 */
?@property(strong, nonatomic)UILabel *sectionLable;
?/*! 背景View */
?@property(nonatomic, strong) UIImageView *bubbleBackgroundView;
在.m中聲明一些全局變量 方便日后容易改動(dòng)
#define kheight 90? ? //名片的高度
?#define kwidth? 220? //名片的寬度
?#define kSpacing 7? ? //個(gè)控件之間的間隔
?#define kBubbleSharp 10 //氣泡尖尖的寬度
?#define kheaderHeight 60 //頭像的寬高
當(dāng)應(yīng)用自定義消息時(shí),必須實(shí)現(xiàn)該方法來(lái)返回cell的Size。
其中,extraHeight是Cell根據(jù)界面上下文,需要額外顯示的高度(比如時(shí)間、用戶名的高度等)。
一般而言,Cell的高度應(yīng)該是內(nèi)容顯示的高度再加上extraHeight的高度。
+ (CGSize)sizeForMessageModel:(RCMessageModel *)model
? ?withCollectionViewWidth:(CGFloat)collectionViewWidth
?referenceExtraHeight:(CGFloat)extraHeight {
//由于名片的高度,寬度一定 所以 下面這句代碼是沒(méi)有用途的
?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;
?}
創(chuàng)建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方法獲取數(shù)據(jù)
- (void)setDataModel:(RCMessageModel *)model {
?[super setDataModel:model];
?[self setAutoLayout];
}
根據(jù)model對(duì)控件在cell上布局,其中分為接收方和發(fā)送方
?-(void)setAutoLayout {
?//創(chuàng)建控件
?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 是不對(duì)的)
?CGSize bubbleBackgroundViewSize =CGSizeMake(kwidth, kheight);
?//消息內(nèi)容的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);
?//消息內(nèi)容的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);
?//自己發(fā)消息
?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);
?//消息內(nèi)容的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)];
?}
?}
點(diǎn)擊問(wèn)題部分的手勢(shì),利用這個(gè)在視圖頁(yè)面進(jìn)行交互
- (void)tapTextMessage:(UIGestureRecognizer *)gestureRecognizer {
? ? ?if ([self.delegate respondsToSelector:@selector(didTapMessageCell:)]) {
? ? ? ? ?[self.delegate didTapMessageCell:self.model];
? ? ?}
?}
長(zhǎng)按手勢(shì),利用這個(gè)在視圖頁(yè)面進(jìn)行交互
- (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];
? ? ?}
?}

效果圖