UIKit版本:
9.7.0
场景:
基于UIKit实现业务的自定义消息
实现过程:
实现自定义消息解析器
(1)构建解析器
类似于NIMSDK的自定义消息收发,业务层需要注册自定义消息解析器,用来编解码自定义消息内容。其实就是json->NECustomAttachment和NECustomAttachment->json的过程,sdk通过代理回调分别暴露了encode和decode方法给业务层,由业务层把自定义消息的json内容转成message的attchment和把attachment转成json用来存储和传输。
UIKit本身实现了一个NECustomAttachment类,这个类就是用来编解码自定义消息内容的,业务层可以直接继承这个类来快速实现自定义消息的编解码。
示例
import NEChatUIKit
import NIMSDK
import UIKit
public class CustomAttachment: NECustomAttachment {
public var goodsName = "name"
public var goodsURL = "url"
override public func encode() -> String {
// 自定义序列化方法之前必须调用父类的序列化方法
let neContent = super.encode()
var info: [String: Any] = getDictionaryFromJSONString(neContent) as? [String: Any] ?? [:]
info["goodsName"] = goodsName
info["goodsURL"] = goodsURL
let jsonData = try? JSONSerialization.data(withJSONObject: info, options: [])
var content = ""
if let data = jsonData {
content = String(data: data, encoding: .utf8) ?? ""
}
return content
}
}
public class CustomAttachmentDecoder: NECustomAttachmentDecoder {
override public func decodeCustomMessage(info: [String: Any]) -> CustomAttachment {
// 自定义反序列化方法之前必须调用父类的反序列化方法
let neCustomAttachment = super.decodeCustomMessage(info: info)
let customAttachment = CustomAttachment(customType: neCustomAttachment.customType,
cellHeight: neCustomAttachment.cellHeight,
data: neCustomAttachment.data)
if customAttachment.customType == 20 {
customAttachment.cellHeight = 100
}
customAttachment.goodsName = info["goodsName"] as? String ?? ""
customAttachment.goodsURL = info["goodsURL"] as? String ?? ""
return customAttachment
}
}
继承自NECustomAttachment的CustomAttachment,可以自行添加需要的类属性,如
goodsName和goodsURL
在encode里面,把当前类的goodsName和goodsURL等属性值以key["goodsName"]value[self.goodsName]的形式添加到json串里面,return给sdk
在decodeCustomMessage里面,sdk返回自定义消息的json串,业务层根据json串内容,把对应的key value赋值给attachment对象,return给sdk
(2)注册解析器
解析器越早注册越好,因为登录之后就会收到离线和漫游消息,如果有自定义消息就会走解析器。可以在登录成功的回调里面添加如下代码注册:
NIMCustomObject.registerCustomDecoder(CustomAttachmentDecoder())
发消息
发消息的时候,需要构建CustomAttachment对象赋值给NIMCustomObject对象,再赋值给NIMMessage,然后send这个NIMMessage对象就可以了。
所以自定义消息发送的关键是CustomAttachment的构建和赋值,UIKit提供构建方法CustomAttachment(customType: customMessageType, cellHeight: 100, data: data)
需要注意的是,这里的customType业务层自定指定,不要重复。cellHeight决定了最终展示在消息详情页面的时候这个消息的cell高度,data可以传data = ["type": customMessageType],customMessageType值同customType。
然后就是CustomAttachment的业务参数赋值,如goodsName和goodsURL
示例
let data = ["type": customMessageType]
let attachment = CustomAttachment(customType: customMessageType, cellHeight: 100, data: data)
attachment.goodsName = NIMSDK.shared().loginManager.currentAccount()
let message = NIMMessage()
let object = NIMCustomObject()
object.attachment = attachment
message.messageObject = object
NIMSDK.shared().chatManager.send(message, to: viewmodel.session) { error in
print("send custom message error : ", error?.localizedDescription as Any)
}
实现自定义消息cell
以上说的还都是非UI的逻辑处理,真正要自定义绘制消息样式是在自定义消息的cell里面
消息的cell,UIKit已经实现了很多,如文本消息,图片消息等,如果要完全起一个空白cell(不包含任何原有元素,如头像),可以继承NEChatBaseCell实现。
import NEChatUIKit
import UIKit
class CustomChatCell: NEChatBaseCell {
public var testLabel = UILabel()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
testLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(testLabel)
NSLayoutConstraint.activate([
testLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
testLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
])
testLabel.font = UIFont.systemFont(ofSize: 14)
testLabel.textColor = UIColor.black
}
override func setModel(_ model: MessageContentModel, _ isSend: Bool) {
print("this is custom message")
testLabel.text = "this is custom message"
}
}
init方法里面添加自定义view,自定义绘制UI,重写setModel方法,在这个方法里面,sdk会返回消息的model内容MessageContentModel,可以在这里给自己绘制的UI赋值。
如果想要cell包含原有的一些元素(如头像 时间tip),可以继承NormalChatMessageBaseCell或者FunChatMessageBaseCell,前者是基础版UI,后者是通用版UI的消息基础cell。
示例
import NEChatUIKit
import UIKit
import NIMSDK
class CustomChatCell: /*NEChatBaseCell*/NormalChatMessageBaseCell {
public var backView = UIView()
public var testLabel = UILabel()
var rigntConstraint: NSLayoutConstraint?
var leftConstraint: NSLayoutConstraint?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
backView.backgroundColor = .orange
backView.layer.cornerRadius = 10
backView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(backView)
NSLayoutConstraint.activate([
backView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
backView.widthAnchor.constraint(equalToConstant: 140),
backView.heightAnchor.constraint(equalToConstant: 100),
])
leftConstraint = backView.rightAnchor.constraint(equalTo: contentView.leftAnchor, constant: 200)
leftConstraint?.isActive = false
rigntConstraint = backView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -60)
rigntConstraint?.isActive = true
testLabel.translatesAutoresizingMaskIntoConstraints = false
backView.addSubview(testLabel)
NSLayoutConstraint.activate([
testLabel.centerXAnchor.constraint(equalTo: backView.centerXAnchor),
testLabel.centerYAnchor.constraint(equalTo: backView.centerYAnchor),
testLabel.widthAnchor.constraint(equalToConstant: 100),
testLabel.heightAnchor.constraint(equalToConstant: 80)
])
testLabel.font = UIFont.systemFont(ofSize: 14)
testLabel.numberOfLines = 0
testLabel.textColor = UIColor.black
}
override func setModel(_ model: MessageContentModel, _ isSend: Bool) {
super.setModel(model, isSend)
if (isSend) {
rigntConstraint?.isActive = true
leftConstraint?.isActive = false
} else {
rigntConstraint?.isActive = false
leftConstraint?.isActive = true
}
print("this is custom message")
let object : NIMCustomObject = model.message?.messageObject as! NIMCustomObject
let attachment : CustomAttachment = object.attachment as! CustomAttachment
testLabel.text = attachment.goodsName
}
}
setModel里面需要调用super.setModel(model, isSend)
实现自定义最近会话cell
最近会话cell重写主要是修改最近会话最后一条消息的内容展示的,如最后一条消息是名片消息,需要在最近会话显示【名片消息】。
继承ConversationListCell,重写configData方法,给subTitle重新赋值就可以
示例
import NEConversationUIKit
import UIKit
open class CustomConversationListCell: ConversationListCell {
// 新增 UI 元素,用于展示在线状态
private lazy var onlineView: UIImageView = {
let notify = UIImageView()
notify.translatesAutoresizingMaskIntoConstraints = false
notify.image = UIImage(named: "about_yunxin")
return notify
}()
override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// 头像右下角
contentView.addSubview(onlineView)
NSLayoutConstraint.activate([
onlineView.rightAnchor.constraint(equalTo: headImge.rightAnchor),
onlineView.bottomAnchor.constraint(equalTo: headImge.bottomAnchor),
onlineView.widthAnchor.constraint(equalToConstant: 12),
onlineView.heightAnchor.constraint(equalToConstant: 12),
])
}
public required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// 此方法用于数据和 UI 的绑定,可在此处在数据展示前对数据进行处理
override open func configData(sessionModel: ConversationListModel?) {
super.configData(sessionModel: sessionModel)
subTitle.text = "[名片消息]"
}
}
会话详情页面展示消息
上面准备好了自定义消息的cell:
CustomChatCell
怎么把这个cell注入到UIKit里面呢,以单聊为例
(1)需要继承会话详情页面
在这个页面把自定义cell注入到消息列表中,示例
class ChatViewController: P2PChatViewController {
let customMessageType = 20
override func viewDidLoad() {
NEChatUIKitClient.instance.regsiterCustomCell(["\(customMessageType)": CustomChatCell.self])
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
需要在super.viewDidLoad()之前注入
这个customMessageType是自己定义的自定义消息的cell类型,可以同发消息时候的消息类型customMessageType。通过regsiterCustomCell方法注入cell。
(2)修改默认单聊消息详情页面为自定义详情页面
Router.shared.register(PushP2pChatVCRouter) { param in
print("param:\(param)")
let nav = param["nav"] as? UINavigationController
guard let session = param["session"] as? NIMSession else {
return
}
let anchor = param["anchor"] as? NIMMessage
let p2pChatVC = ChatViewController(session: session, anchor: anchor)
for (i, vc) in (nav?.viewControllers ?? []).enumerated() {
if vc.isKind(of: ChatViewController.self) {
nav?.viewControllers[i] = p2pChatVC
nav?.popToViewController(p2pChatVC, animated: true)
return
}
}
if let remove = param["removeUserVC"] as? Bool, remove {
nav?.viewControllers.removeLast()
}
nav?.pushViewController(p2pChatVC, animated: true)
}
在登录成功之后,重注册PushP2pChatVCRouter路由,这样从会话列表页面跳转就跳转到自定义的
ChatViewController页面了,从而使注入cell的代码生效。
会话列表展示消息类型
同样的最近会话列表的自定义cell注入也需要继承会话列表页面ConversationController,通过cellRegisterDic[customMessageType] = CustomConversationListCell.self注入自定义的CustomConversationListCell。
示例
import UIKit
import NIMSDK
import NEConversationUIKit
class ViewController: ConversationController, NEBaseConversationControllerDelegate {
let customMessageType = 20
override func viewDidLoad() {
delegate = self
// 自定义cell, [ConversationListModel.customType: 需要注册的自定义cell]
cellRegisterDic[customMessageType] = CustomConversationListCell.self
super.viewDidLoad()
// Do any additional setup after loading the view.
}
public func onDataLoaded() {
guard let conversationList = viewModel.conversationListArray else { return
}
conversationList.forEach { model in
let message = model.recentSession?.lastMessage
if (message?.messageType == .custom) {
model.customType = customMessageType
}
}
tableView.reloadData()
}
}
需要在super.viewDidLoad()之前注入
需要注意的是,这里也需要指定一个类型customMessageType。并且需要实现
NEBaseConversationControllerDelegate,在onDataLoaded方法里面绑定会话列表数据model的
customType。也就是cell的type和model的type一致,这样sdk加载数据的时候才知道使用哪个cell展示。
网友评论