TUIChatKit 默认实现了文本、图片、语音、视频、文件等基本消息类型的发送和展示,如果这些消息类型满足不了您的需求,您可以新增自定义消息类型。
自定义消息
如果基本消息类型不能满足您的需求,您可以根据实际业务需求自定义消息。下文以发送一条可跳转至浏览器的超文本作为自定义消息为例,帮助您快速了解实现流程。自定义消息样式如下图所示:

展示自定义消息
在 TUIChatKit 中,您只需要实现一个遵循
MessageContentView 协议的气泡内容视图,并在 App 初始化时通过 MessageListView.registerCustomMessageCell 注册即可,聊天页 ChatPage 无需额外配置。自定义消息的内容以 String 格式存储在
CustomMessagePayload.customData 中,建议使用 JSON 格式,并通过 businessID 字段来唯一标识这条消息类型。定义自定义消息数据结构
定义一个结构体,用于描述自定义消息解析后的结构,并提供从 JSON String 解析的方法。以上述包含链接及文本的自定义消息格式举例:
struct CustomLinkMessage {static let businessID = "text_link"let text: String?let link: String?static func from(customData: String?) -> CustomLinkMessage? {guard let customData = customData, !customData.isEmpty,let data = customData.data(using: .utf8),let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {return nil}return CustomLinkMessage(text: dict["text"] as? String, link: dict["link"] as? String)}static func from(message: MessageInfo) -> CustomLinkMessage? {guard case .custom(let payload) = message.messagePayload else { return nil }return from(customData: payload.customData)}}
实现自定义消息渲染器
实现一个遵循
MessageContentView 协议的 UIView,在 bind(message:context:) 中解析 CustomLinkMessage 并把文字和链接渲染到视图上,同时为气泡添加点击跳转事件。bind 的 MessageContentContext 提供了 isSelf(是否自己发送)、isMultiSelectMode(是否多选模式)等上下文,主题颜色可通过 TUIChatKitTheme.colors 获取。示例代码如下:
import AtomicXCoreimport SnapKitimport TUIChatKitimport UIKitfinal class CustomLinkMessageContentView: UIView, MessageContentView {private static let maxBubbleWidth: CGFloat = 245private static let horizontalPadding: CGFloat = 16private static let verticalPadding: CGFloat = 12private static let linkTopSpacing: CGFloat = 8private let textLabel = UILabel()private let linkLabel = UILabel()private var linkURL: URL?init() {super.init(frame: .zero)addSubview(textLabel)addSubview(linkLabel)textLabel.snp.makeConstraints { make inmake.top.equalToSuperview().offset(Self.verticalPadding)make.leading.equalToSuperview().offset(Self.horizontalPadding)make.trailing.lessThanOrEqualToSuperview().offset(-Self.horizontalPadding)make.width.lessThanOrEqualTo(Self.maxBubbleWidth - Self.horizontalPadding * 2)}linkLabel.snp.makeConstraints { make inmake.top.equalTo(textLabel.snp.bottom).offset(Self.linkTopSpacing)make.leading.equalToSuperview().offset(Self.horizontalPadding)make.trailing.lessThanOrEqualToSuperview().offset(-Self.horizontalPadding)make.bottom.equalToSuperview().offset(-Self.verticalPadding)}textLabel.numberOfLines = 0textLabel.font = .systemFont(ofSize: 16)textLabel.preferredMaxLayoutWidth = Self.maxBubbleWidth - Self.horizontalPadding * 2linkLabel.numberOfLines = 1linkLabel.font = .systemFont(ofSize: 14)addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}func bind(message: MessageInfo, context: MessageContentContext) {let linkMessage = CustomLinkMessage.from(message: message)let colors = TUIChatKitTheme.colorstextLabel.text = linkMessage?.texttextLabel.textColor = message.isSentBySelf ? colors.textColorAntiPrimary : colors.textColorPrimarylet link = linkMessage?.link?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""linkURL = URL(string: link)linkLabel.isHidden = link.isEmptylinkLabel.text = "查看详情"linkLabel.textColor = colors.textColorLinkisUserInteractionEnabled = !link.isEmpty && !context.isMultiSelectMode}@objc private func handleTap() {guard let url = linkURL else { return }UIApplication.shared.open(url)}}
注册渲染器并自定义会话摘要
在 App 初始化时调用一次
MessageListView.registerCustomMessageCell 完成注册。其中 businessID 必须与消息 customData 中的 businessID 字段一致,TUIChatKit 会据此找到对应的渲染器;summaryProvider 用于生成该消息的文字摘要,会同时应用到会话列表的"最后一条消息"预览和消息列表内引用回复的预览。示例代码如下:
import AtomicXCoreimport TUIChatKit// App 启动时调用一次即可,例如在 AppDelegate 的 didFinishLaunchingWithOptions 中MessageListView.registerCustomMessageCell(businessID: CustomLinkMessage.businessID,summaryProvider: { payload inCustomLinkMessage.from(customData: payload.customData)?.text},makeContentView: {CustomLinkMessageContentView()})
实现后的会话列表预览效果如下:

发送自定义消息
自定义消息发送逻辑
把要发送的内容转成 JSON String 放入
CustomSendMessagePayload 的 customData 中,再调用 MessageInputStore 的 sendMessage 接口发送。核心代码如下:import AtomicXCorelet payload: [String: Any] = ["businessID": "text_link","text": "欢迎加入云通信IM大家庭!","link": "https://cloud.tencent.com/document/product/269/3794"]if let data = try? JSONSerialization.data(withJSONObject: payload),let json = String(data: data, encoding: .utf8) {let customPayload = CustomSendMessagePayload(customData: json, description: "欢迎加入云通信IM大家庭!")MessageInputStore.create(conversationID: conversationID).sendMessage(payload: .custom(customPayload), option: nil, completion: nil)}
注意:
自定义消息内容必须为 JSON 格式。其中 "businessID" 字段为必填项。可根据业务需求添加其他字段,单条消息大小上限为 12KB。例如:
{"businessID":"text_link","link":"https://cloud.tencent.com/document/product/269/3794","text":"欢迎加入云通信IM大家庭!"}
每一种自定义消息都必须有唯一的 businessID(区分大小写),不可与其他自定义消息重复,也不能和 TUIChatKit 内置自定义消息的 businessID 重复。
在输入框的“更多”面板中增加一个自定义消息发送按钮
您可以在输入框的"更多"面板中增加一个自定义消息发送按钮,点击时创建并发送上述自定义消息。如下图所示:

通过
ChatMessageInputConfig 的 actionCustomizer 追加一个自定义功能项,并把配置传入 ChatPage 即可。代码如下:import AtomicXCoreimport TUIChatKitimport UIKitlet inputConfig = ChatMessageInputConfig(actionCustomizer: { editor ineditor.add(MessageInputMenuAction(ID: "customLink",title: "自定义消息",iconName: "",icon: UIImage(systemName: "paperplane.fill"),onClick: {// 组装并发送自定义消息(代码同上)let payload: [String: Any] = ["businessID": "text_link","text": "欢迎加入云通信IM大家庭!","link": "https://cloud.tencent.com/document/product/269/3794"]guard let data = try? JSONSerialization.data(withJSONObject: payload),let json = String(data: data, encoding: .utf8) else { return }let customPayload = CustomSendMessagePayload(customData: json, description: "欢迎加入云通信IM大家庭!")MessageInputStore.create(conversationID: conversationID).sendMessage(payload: .custom(customPayload), option: nil, completion: nil)}))})let chatPage = ChatPage(conversation: conversation, messageInputConfig: inputConfig)navigationController.pushViewController(chatPage, animated: true)
MessageInputMenuAction 的 icon 支持直接传入 UIImage(例如 SF Symbols 图标),也可以通过 iconName 指定资源包中的图片名;ID 为该功能项的唯一标识,不可与内置功能项重复。