最近把公司项目的聊天模块从”XMPP”转到”网易云信”官网、github。在转的过程中,上手很快,基本上没遇到什么难题,很多程度上感谢云信的NIMKit。以前也接触过几个IM SDK服务商的代码,那个时候看他们的代码根本没什么欲望,但这次看云信的代码有种被吸引的感觉,恨不得一下子把它的代码全部看完,封装的很好,扩展性很强(ps:就我目前的水平只能说出这些优点)。对于一般的聊天UI完全可以满足,就算不用网易的IM SDK,但他们的代码真的值得一下(尽管他们的UIKit代码注释比较少)。
在看本文之前,请先看一下他们官方的github简介。
##Base tips
####cell的组成结构
对于聊天”MessageCell”的介绍,一定要记住下面这张图片,以及相关参数的解释。
- 蓝色区域:为具体内容,如文字 UILabel ,图片 UIImageView 等 。(对应的
NIMMessageModel
对象的contentSize
属性)。注:NIMMessageModel为消息(NIMMessage) 在NIMKit中的封装。这个封装主要是为了对计算结果和布局配置进行缓存,以避免反复的计算和读取相同的信息,从而提高应用性能。- 绿色区域:为消息的气泡,具体的内容和气泡之间会有一定的内间距,这里为 contentViewInsets 。(对应的
NIMMessageModel
对象的contentViewInsets
属性)- 紫色区域:为整个 UITableViewCell ,具体的气泡和整个cell会有一定的内间距,这里为 cellInsets 。(对应的
NIMMessageModel
对象的bubbleViewInsets
属性)
####config配置协议
在聊天界面有几个config配置代理,先熟悉一下。
- NIMSessionConfig:消息对应的session配置。如:录音、输入框、表情、更多等操作的选择;点击”+”号出来的多媒体按钮;是否禁用输入控件;输入控件的最大长度;输入控件的placeholder;一次最多消息的消息内容;间隔多久显示时间;语音红点是否禁用;是否自动切换成听筒模式;是否自动获取历史消息;消息数据提供器;消息的排版配置等。可以说这个是贯穿整个聊天模块的配置,修改聊天界面一般就得调整这里。
- NIMCellLayoutConfig:消息对应的布局配置。我们可以在这个config里面根据消息类型是否显示头像、姓名、头像与姓名之间的margin等;然后你会在项目里面看到自定义消息类型对应的NTESSessionCustomLayoutConfig,以及default默认的配置NIMCellLayoutDefaultConfig;
- NIMSessionContentConfig:消息内容配置。这个配置主要是为
NIMSessionMessageContentView
(请看下面对 聊天 NIMMessageCell.h 的介绍)对象为设置的。
####聊天 NIMMessageCell.h
先来看看头文件定义的属性
1 | @property (nonatomic, strong) NIMAvatarImageView *headImageView; |
NIMSessionMessageContentView,顾名思义就是MessageCell的内容View(包括下面的bubble气泡View)。而 NIMSessionContentConfig 配置主要是配置 contentSize、contentViewInsets以及这个配置所应的 messageContentView 类名(NIMSessionMessageContentView的子类,每种聊天类型对应一个messageContentView)。注意,这里并没有提到 bubbleViewInsets,因为气泡隔cell的距离不会因不同类型而改变,我们只需在 cellLayoutConfig 里面处理即可,当然想要做到不同的话,也可以在 NIMSessionContentConfig 配置里面增加一个协议方法。注意 NIMSessionMessageContentView 是继承自 UIControl,这样不仅能处理点击事件,还能很好的处理点击高亮的效果。
##NIMSessionViewController(聊天回话控制器基类)
先来看看最重要的计算高度方法
1 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath |
某个数据源所对应的高度就是三个颜色区域的高度之和(contentSize.height + contentViewInsets.top + contentViewInsets.bottom + bubbleViewInsets.top + bubbleViewInsets.bottom)。
然后我们在来看看layoutConfig:
方法:
1 | - (void)layoutConfig:(NIMMessageModel *)model{ |
其实这里就是,先配置model的sessionConfig,然后配置layoutConfig,配置完后就去计算该model所对应内容的contentSize。注:layoutConfigWithMessage:
方法是自定义消息类型需要处理,还有记得在写代码中做好判nil的处理,如果为nil的话给default值。
好了,现在到了一个我当时比较蛋疼的地方了,请看下图
看到很多的方法,仔细看看,除了layoutConfig的配置方法以外,还有很多Attachment
(Attachment 是属于自定义消息的配置协议)结尾的方法,其实这里应该只会提示NIMCellLayoutDefaultConfig
和NTESSessionCustomLayoutConfig
(NTESChatroomCellLayoutConfig
聊天室的布局配置请忽略)才对,它们只是方法名相同,应该是Xcode抽风而导致的。
####NIMCellLayoutDefaultConfig计算contentSize
先看三个相关部分的代码
######NIMCellLayoutDefaultConfig
1 | - (CGSize)contentSize:(NIMMessageModel *)model cellWidth:(CGFloat)cellWidth{ |
######NIMSessionContentConfigFactory
1 | - (instancetype)init |
######NIMImageContentConfig
1 | #import "NIMBaseSessionContentConfig.h" |
1 | @interface NIMTextContentConfig() |
这里就是实现了NIMSessionContentConfig
配置协议,我举例一个文本消息类型的sessionContentView的处理方式,其他类型是一样的处理方法,实现相关配置协议方法即可。你可能会想到如果某个sessionContentView上面的元素有很多时该怎么处理,我该不会把某个sessionContentView的元素都定义一次,然后全部赋值再计算contentSize么?我将会在说自定义消息类型的时候谈谈我简单的处理方式。
这里我个人觉得有两点可以改变一下。
NIMBaseSessionContentConfig
的NIMMessage
对象应该改为NIMMessageModel
对象比较好。因为我需要用到contentSize,根据contentSize来设置控件的宽度适应屏幕。所以我在自定义的消息里面,将NTESCustomAttachmentInfo
协议需要传入的NIMMessage
对象改为NIMMessageModel
对象。- 在返回
contentView
类名时,改为NSStringFromClass([NIMSessionTextContentView Class])
会好点,怕输入字符串时时产生错误嘛。
在NIMSessionContentConfigFactory
类里面定义了基本消息类型所对应的contentConfig
配置协议(注意,在云信demo里面,每个sessionContentView都对应一个sessionContentConfig)。请看NIMUnsupportContentConfig
判nil处理,如果没有这段判断处理,你在添加自定义消息时候,忘记在 NTESSessionCustomLayoutConfig
类的supportAttachmentType
方法里面添加你的自定义消息,程序就会崩溃。ps:防止崩溃,请从细节做起,谢谢!
那NIMCellLayoutDefaultConfig
计算contentSize就简单明了了,就是调用相关 sessionContentConfig 的方法嘛。
####NNTESSessionCustomContentConfig计算contentSize
当我们看到NTESSessionCustomLayoutConfig
类时,有两个地方是值得我们注意,也是与NIMCellLayoutDefaultConfig
不同的地方。
- 一个
NTESSessionCustomContentConfig
类的属性supportAttachmentType
内部方法,用来获取customLayoutConfig直接的类型。
######NTESSessionCustomContentConfig
它有一个NIMMessage
类型的public属性,而在介绍NIMBaseSessionContentConfig
配置协议时,我有说过建议将它的NIMMessage
对象改为NIMMessageModel
对象,在这里我也同样建议,原因上面有提过。
请看它的.m文件:
1 | @interface NTESSessionCustomContentConfig() |
attachmentInfo对象代表不同类型的自定义消息,只要它遵守NTESCustomAttachmentInfo
协议即可。(ps:其实NTESCustomAttachmentInfo
协议就相当于上面基本消息类型所对应的NIMSessionContentConfig
协议;注:这里所谓的基本消息类型,即云信SDK已定义的消息类型,相对于自定义消息类型而言而已。)
下面来看看 NTESCustomAttachmentInfo 协议(已添加注释)。
1 | @protocol NTESCustomAttachmentInfo <NSObject> |
在这里我提出两点建议
NIMMessage
对象改为NIMMessageModel
对象;cellContent:
、contentSize: cellWidth:
、contentViewInsets:
这三个方法改为@required
类型的;方法名前面加上attachmentInfo
与NIMSessionContentConfig
协议的相关方法作为区分。
######复杂自定义sessionContentView的简单处理方式
上面在介绍NIMBaseSessionContentConfig
配置协议时,我有提到如果某个sessionContentView上面的元素有很多时该怎么处理。下面我说说我的处理方式。
- 把计算contentSize和contentViewInsets的方法丢到contentView里面,这样一来,那么只要在attachMentInfo里面调用所属contentView的计算方法。
- 我会在
NIMSessionMessageContentView
类里面增加两个方法
1 | - (CGSize)attachmentInfoViewContentSize:(NIMMessageModel *)messageModel cellWidth:(CGFloat)width; |
- 在具体的contentView里面,我定义方法,它有一个Bool类型的isInit(是否初始化)入参,在这个方法里面我创建和实例变量一样的临时变量,当
attachmentInfoViewContentSize: cellWidth
方法调用它时,我只是为了方便计算contentSize,如果是initSessionMessageContentView
方法调用时,我就将相应的临时变量赋值给例变量。