自定义会话消息 UI
更新时间: 2025/09/11 16:17:41
IM UIKit 的会话模块(NEChatUIKit)提供会话界面 UI 的自定义配置项,助您快速实现该界面的 UI 个性化定制。会话消息模块(NEChatUIKit)提供以下两种方式对会话消息的 UI 进行个性化定制。
-
通过接口配置的方式自定义 UI。
-
通过源码修改的方式自定义 UI。
请优先使用接口配置方式。如果接口中的配置项满足您的界面个性化需求,请优先使用接口配置方式。
自定义功能概述
UI 元素自定义
会话消息界面可自定义的 UI 元素包括但不限于下图中的标注项。
ChatUIConfig 是整个会话消息的配置文件,其中 UI 实例化对象类型为 ChatUIConfig。
您可通过 ChatUIConfig 中的个性化配置项修改会话界面 UI 元素,如消息体、输入按钮和消息长按菜单等,具体如下:
属性 |
类型 | 说明 |
|---|---|---|
messageItemClick |
(UITableViewCell, MessageContentModel?) -> Void | 用于设置消息体中的单击事件处理 |
-
messageProperties |
MessageProperties | 消息页面的 UI 个性化定制,包括常用的字体颜色、大小、是否可见、背景色等属性等。单击 MessageProperties 左侧的 + 展开展示 MessageProperties 个性化配置项 更多信息。 |
selfMessageTextSize |
CGFloat | 自己发送的消息的字体大小(文本类型)。 |
receiveMessageTextSize |
CGFloat | 接收到的消息的字体大小(文本类型)。 |
selfMessageTextColor |
UIColor | 自己发送的消息的字体颜色(文本类型)。 |
receiveMessageTextColor |
UIColor | 接收到的消息的字体颜色(文本类型)。 |
selfMessageBgColor |
UIColor | 自己发送的消息体的背景色,具体自定义示例见下文的 修改消息体背景色。 |
receiveMessageBgColor |
UIColor | 接收到的消息体的背景色,具体自定义示例见下文的 修改消息体背景色。 |
selfMessageBgImage |
UIImage | 自己发送的消息体的背景气泡。 |
receiveMessageBgImage |
UIImage | 接收到的消息体的背景气泡。 |
backgroundImageCapInsets |
UIEdgeInsets | 背景图片拉伸参数(边距偏移)。 |
userNickColor |
UIColor | 不设置头像的用户所展示的文字头像中的文字颜色。 |
userNickTextSize |
CGFloat | 不设置头像的用户所展示的文字头像中的文字字体大小。 |
messageTextColor |
UIColor | 文本消息字体颜色。 |
messageTextSize |
CGFloat | 文本消息字体大小。 |
avatarType |
NEChatAvatarType | 头像类型,NEChatAvatarType.rectangle = 1 代表矩形,NEChatAvatarType.cycle 代表圆形。 |
avatarCornerRadius |
CGFloat | 头像的圆角大小,仅在 avatarType = NEChatAvatarType.rectangle 时生效。 |
timeTextSize |
CGFloat | 消息列表中,时间字体大小。 |
timeTextColor |
UIColor | 消息列表中,时间字体颜色。 |
signalBgColor |
UIColor | 被标记消息的背景色。 |
showP2pMessageStatus |
Bool | 单聊中是否展示已读未读状态。 |
showTeamMessageStatus |
Bool | 群聊中是否展示已读未读状态。 |
showTeamMessageNick |
Bool | 群聊中是否展示好友昵称。 |
showTitleBar |
Bool | 会话界面是否展示标题栏,具体自定义示例见下文的 隐藏界面标题栏。 |
showTitleBarRightIcon |
Bool | 是否展示标题栏右侧图标按钮。 |
titleBarRightRes |
UIImage | 设置标题栏右侧图标按钮展示图标,具体自定义示例见下文的 修改界面标题右侧图标。 |
titleBarRightClick |
() -> Void | 标题栏右侧图标的单击事件。 |
chatViewBackground |
UIColor | 设置会话界面背景色,具体自定义示例见下文的 修改会话界面背景色。 |
customController |
(ChatViewController) -> Void | 消息页面中,获取页面的整个视图。可用于在页面中添加、移除或者修改布局元素。 |
chatInputBar |
(inout [UIButton]) -> Void | 消息页面中,文本输入框下方 tab 按钮定制。 |
chatInputMenu |
(inout [NEMoreItemModel]) -> Void | 消息页面中,输入框按钮定制。可用于修改输入框按钮以及更多中按钮的增加、修改和删除。 |
chatPopMenu |
(inout [OperationItem], MessageContentModel?) -> Void | 消息页面中,长按消息体弹出菜单定制。用于对长按菜单的增加、删除和修改。 |
popMenuClick |
(OperationItem) -> Void | 消息页面中,长按消息体弹出菜单定制。用于对长按菜单的单击事件的修改,可修改默认实现和自定义菜单的单击。 |
fileSizeLimit |
Double | 消息页面中,发送文件大小限制(单位:MB),默认 200。 |
bodyTopView |
UIView | 消息列表界面上方的小块视图,可在子类直接获取视图进行样式修改。用于在消息详情和顶部标题视图中间增加新的 UI 元素,具体自定义示例见下文的 会话消息标题栏下方扩展视图。 |
bodyBottomView |
UIView | 输入框视图上方的小块视图,可在子类直接获取视图进行样式修改,用于在消息详情和底部输入框中间增加新的 UI 元素,具体自定义示例见下文的 输入框上方区域扩展视图。 |
界面布局自定义
除了界面的 UI 元素,您也可对界面的布局进行自定义。
在 NEChatUIKit 模块中,所有的 Cell 都继承于 NEChatBaseCell。
以基础版皮肤中的文本消息为例,其继承链路为:ChatMessageTextCell -> NormalChatMessageBaseCell -> NEBaseChatMessageCell -> NEChatBaseCell。
-
基础信息(如背景颜色,头像或者昵称)会在上层的
NEBaseChatMessageCell中赋值,需要单独处理的逻辑需要在各自对应的业务 Cell 中完成。 -
每种类型的消息体中都包含左右两边消息展示所需要的所有元素,并根据消息方向进行了布局,在展示前可以根据消息方向控制元素的显隐以及完成元素对象的赋值。
会话界面的默认布局如下图所示:
该界面各视图的说明如下:
属性 |
类型 | 说明 |
|---|---|---|
titleBarView |
UIView | 聊天界面标题,可通过 navigationView 来获取视图进行样式修改。 |
navigationView |
TabNavigationView | 聊天界面的标题视图,可在子类直接获取视图进行样式修改,具体自定义示例请参考下文的 自定义标题栏按钮。 |
bodyTopView |
UIView | 消息列表界面上方的小块视图,可在子类直接获取视图进行样式修改。用于在消息详情和顶部标题视图中间增加新的 UI 元素,具体自定义示例请参考下文的 会话消息标题栏下方扩展视图。 |
bodyView |
UIView | 消息列表的内容视图,可在子类直接获取视图进行样式修改。布局中包含 brokenNetworkView 错误提示视图和 contentView 列表视图。 |
brokenNetworkView |
NEBrokenNetworkView | 聊天界面错误提示视图,可在子类直接获取视图进行样式修改。 |
contentView |
UIView | 消息列表的列表视图,可在子类直接获取视图进行样式修改。布局中包含 tableView 消息列表和 emptyView 空数据提示视图。 |
tableView |
UITableView | 消息详情列表,可在子类直接获取视图进行样式修改。 |
emptyView |
NEEmptyDataView | 聊天界面空数据提示视图,可在子类直接获取视图进行样式修改。 |
bottomView |
UIView | 消息列表底部视图,可在子类直接获取视图进行样式修改,布局中包含 bodyBottomView 扩展视图和 chatInputView 输入框视图。 |
bodyBottomView |
UIView | 输入框视图上方的小块视图,可在子类直接获取视图进行样式修改,用于在消息详情和底部输入框中间增加新的 UI 元素,具体自定义示例请参考下文的 输入框上方区域扩展视图。 |
chatInputView |
UIView | 聊天界面的底部输入框视图,可在子类中进行样式修改。具体自定义示例请参考下文的 自定义输入框左右间距chatInputView.stackView 为输入框底部工具栏视图,可添加或删除按钮。具体自定义示例请参考下文的 自定义底部工具条 和 自定义 更多 功能页。 |
通过配置项自定义 UI 示例
会话消息的个性化 UI 样式需要在会话消息界面加载之前配置,例如您可以在 IMKitClient 的 setupIM 方法中设置个性化定制属性。
更多示例代码请参考 app/IMUIKitExample/Custom/CustomConfig.swift
使用系统导航栏(不推荐)
UIKit 默认使用自定义导航栏,如果你需要使用系统导航栏,请在会话消息界面加载之前完成如下配置:
Swift// import NECommonUIKit
NEConfigManager.instance.setParameter(key: useSystemNav, value: true)
Objective-C// #import <NECommonUIKit/NECommonUIKit-Swift.h>
[NEConfigManager.instance setParameterWithKey:@"useSystemNav" value:YES];
修改界面标题栏右侧图标
-
示例代码
SwiftSwiftChatUIConfig.shared.ui.messageProperties.titleBarRightRes = UIImage(named: "image name")Objective-CObjective-CChatUIConfig.shared.ui.messageProperties.titleBarRightRes = [UIImage imageNamed:@"image name"]; -
效果对比
默认 自定义后 

隐藏界面标题栏
-
示例代码
SwiftSwiftChatUIConfig.shared.ui.messageProperties.showTitleBar = falseObjective-CobjChatUIConfig.shared.ui.messageProperties.showTitleBar = NO; -
效果对比
默认 自定义后 

修改会话界面背景色
-
示例代码
SwiftSwiftChatUIConfig.shared.ui.messageProperties.chatViewBackground = UIColor.redObjective-CObjective-CChatUIConfig.shared.ui.messageProperties.chatViewBackground = [UIColor redColor]; -
效果对比
默认 自定义后 

修改消息体背景色
-
示例代码
SwiftSwift//修改自己消息体背景色 ChatUIConfig.shared.ui.messageProperties.selfMessageBgColor = UIColor.green ChatUIConfig.shared.ui.messageProperties.selfMessageBgImage = UIImage() // 隐藏气泡 //修改对方消息体背景色 ChatUIConfig.shared.ui.messageProperties.receiveMessageBgColor = UIColor.gray ChatUIConfig.shared.ui.messageProperties.receiveMessageBgImage = UIImage() // 隐藏气泡Objective-CObjective-C//修改自己消息体背景色 ChatUIConfig.shared.ui.messageProperties.selfMessageBgColor = [UIColor blueColor]; ChatUIConfig.shared.ui.messageProperties.selfMessageBgImage = [UIImage new]; // 隐藏气泡 //修改对方消息体背景色 ChatUIConfig.shared.ui.messageProperties.receiveMessageBgColor = [UIColor blueColor]; ChatUIConfig.shared.ui.messageProperties.receiveMessageBgImage = [UIImage new]; // 隐藏气泡会话消息的消息体背景色与气泡共存,若只需要实现纯色背景色效果,请将默认气泡图片隐藏(置为 nil)
-
效果对比
默认 自定义后 

会话消息标题栏下方扩展视图
可在标题栏和消息详情中间插入扩展视图,例如显示群聊人数、在线状态,插入温馨提示等。
-
示例代码
SwiftSwift/// 消息列表的视图控制器回调,回调中会返回消息列表的视图控制器 /// customTopView:自定义视图 ChatUIConfig.shared.customController = { viewController in // 顶部bodyTopView中添加自定义view(需要设置bodyTopView的高度) self.customTopView.button.setTitle("通过配置项添加", for: .normal) viewController.bodyTopView.backgroundColor = .purple viewController.bodyTopView.addSubview(customTopView) viewController.bodyTopViewHeight = 80 }Objective-CObjective-C/// 消息列表的视图控制器回调,回调中会返回消息列表的视图控制器 /// customTopView:自定义视图 ChatUIConfig.shared.customController = ^(ChatViewController * _Nonnull viewController) { // 顶部bodyTopView中添加自定义view(需要设置bodyTopView的高度) [self.customTopView setTitle:@"通过配置项添加" forState:UIControlStateNormal]; viewController.bodyTopView.backgroundColor = UIColor.purpleColor; [viewController.bodyTopView addSubview:customTopView]; viewController.bodyTopViewHeight = 80; }; -
效果对比
默认 自定义后 

输入框上方区域扩展视图
可在输入框上方插入扩展视图,例如添加候选词条、个性化功能等。
-
示例代码
SwiftSwift/// 消息列表的视图控制器回调,回调中会返回消息列表的视图控制器 /// customTopView:自定义视图 ChatUIConfig.shared.customController = { viewController in // 底部bodyBottomView中添加自定义view(需要设置bodyBottomView的高度) self.customBottomView.button.setTitle("通过配置项添加", for: .normal) viewController.bodyBottomView.backgroundColor = .purple viewController.bodyBottomView.addSubview(customBottomView) viewController.bodyBottomViewHeight = 60 }Objective-CObjective-C/// 消息列表的视图控制器回调,回调中会返回消息列表的视图控制器 /// customTopView:自定义视图 ChatUIConfig.shared.customController = ^(ChatViewController * _Nonnull viewController) { // 底部bodyBottomView中添加自定义view(需要设置bodyBottomView的高度) [self.customTopView setTitle:@"通过配置项添加" forState:UIControlStateNormal]; viewController.bodyBottomView.backgroundColor = UIColor.purpleColor; [viewController.bodyBottomView addSubview:customTopView]; viewController.bodyBottomViewHeight = 60; }; -
效果对比
默认 自定义后 

源码介绍
会话消息
ChatViewController 为会话消息的基类,内部封装了 UITableView。
所有类型的消息都以 Cell 形式展示,需要在 cellRegisterDic 注册列表中声明不同消息体对应的 Cell 类,用于 UITableView 的 Cell 注册。利用面向对象语言的多态性,不同类型的 Cell 重写了父类的 setModel 方法,且每种类型的 Cell 都有与之对应的数据模型。
默认的 cellRegisterDic 注册方法请参考 ChatMessageHelper 工具类中的 getChatCellRegisterDic 方法
对应关系如下:
| 基础版 UI Cell 类别 | 数据模型 | 说明 |
|---|---|---|
| ChatMessageTextCell | MessageTextModel | 文本消息类型 |
| ChatMessageAudioCell | MessageAudioModel | 语音消息类型 |
| ChatMessageImageCell | MessageImageModel | 图片消息类型 |
| ChatMessageVideoCell | MessageVideoModel | 视频消息类型 |
| ChatMessageFileCell | MessageFileModel | 文件消息类型 |
| ChatMessageLocationCell | MessageLocationModel | 地理位置消息类型 |
| ChatMessageCallCell | MessageCallRecordModel | 音视频消息类型 |
| ChatMessageReplyCell | MessageTextModel | 回复(文本)消息类型 |
| ChatMessageRevokeCell | MessageTextModel | 撤回(文本)消息类型 |
| ChatMessageTipCell | MessageTipsModel | 提示消息类型 |
| ChatMessageMultiForwardCell | MessageCustomModel | 合并转发消息类型 |
| ChatMessageRichTextCell | MessageRichTextModel | 换行消息类型 |
| 通用版 UI Cell 类别 | 数据模型 | 说明 |
|---|---|---|
| FunChatMessageTextCell | MessageTextModel | 文本消息类型 |
| FunChatMessageAudioCell | MessageAudioModel | 语音消息类型 |
| FunChatMessageImageCell | MessageImageModel | 图片消息类型 |
| FunChatMessageVideoCell | MessageVideoModel | 视频消息类型 |
| FunChatMessageFileCell | MessageFileModel | 文件消息类型 |
| FunChatMessageLocationCell | MessageLocationModel | 地理位置消息类型 |
| FunChatMessageCallCell | MessageCallRecordModel | 音视频消息类型 |
| FunChatMessageReplyCell | MessageTextModel | 回复(文本)消息类型 |
| FunChatMessageRevokeCell | MessageTextModel | 撤回(文本)消息类型 |
| FunChatMessageTipCell | MessageTipsModel | 提示消息类型 |
| FunChatMessageMultiForwardCell | MessageCustomModel | 合并转发(自定义)消息类型 |
| FunChatMessageRichTextCell | MessageRichTextModel | 换行消息类型 |
赋值处理
-
根据
V2NIMMessage的messageType类型,判断消息类型。 -
针对不同的消息类型,使用对应的数据模型进行解析。
swiftpublic class ChatMessageHelper: NSObject { public static func modelFromMessage(message: NIMMessage) -> MessageModel { var model: MessageModel switch message.messageType { case .MESSAGE_TYPE_VIDEO: model = MessageVideoModel(message: message) case .MESSAGE_TYPE_TEXT: model = MessageTextModel(message: message) case .MESSAGE_TYPE_IMAGE: model = MessageImageModel(message: message) case .MESSAGE_TYPE_AUDIO: model = MessageAudioModel(message: message) case .MESSAGE_TYPE_NOTIFICATION, .MESSAGE_TYPE_TIP: model = MessageTipsModel(message: message) case .MESSAGE_TYPE_FILE: model = MessageFileModel(message: message) case .MESSAGE_TYPE_LOCATION: model = MessageLocationModel(message: message) case .MESSAGE_TYPE_CALL: model = MessageCallRecordModel(message: message) case .MESSAGE_TYPE_CUSTOM: if let type = NECustomAttachment.typeOfCustomMessage(message.attachment) { if type == customMultiForwardType { return MessageCustomModel(message: message, contentHeight: Int(customMultiForwardCellHeight)) } if type == customRichTextType { return MessageRichTextModel(message: message) } // 注册过的自定义消息类型 return MessageCustomModel(message: message, contentHeight: Int(customMultiForwardCellHeight)) } fallthrough default: // 未识别的消息类型,默认为文本消息类型,text 为未知消息体 message.text = chatLocalizable("msg_unknown") model = MessageTextModel(message: message) } return model } } -
在
ChatViewController的 Cell 展示数据源方法中进行赋值。swiftopen func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let model = viewModel.messages[indexPath.row] var reuseId = "\(NEBaseChatMessageCell.self)" if model.replyedModel?.isReplay == true, model.isRevoked == false { if model.customType == customRichTextType { reuseId = "\(MessageType.richText.rawValue)" } else { reuseId = "\(MessageType.reply.rawValue)" } } else { let key = "\(model.type.rawValue)" if model.type == .custom { if model.customType == customMultiForwardType { reuseId = "\(MessageType.multiForward.rawValue)" } else if model.customType == customRichTextType { reuseId = "\(MessageType.richText.rawValue)" } else if NEChatUIKitClient.instance.getRegisterCustomCell()["\(model.customType)"] != nil { reuseId = "\(model.customType)" } else { reuseId = "\(NEBaseChatMessageCell.self)" } } else if model.type == .notification || model.type == .tip { reuseId = "\(MessageType.time.rawValue)" } else if cellRegisterDic[key] != nil { reuseId = key } else { reuseId = "\(NEBaseChatMessageCell.self)" } } let cell = tableView.dequeueReusableCell(withIdentifier: reuseId, for: indexPath) if let c = cell as? NEBaseChatMessageTipCell { if let m = model as? MessageTipsModel { c.setModel(m) } return c } else if let c = cell as? NEBaseChatMessageCell { c.delegate = self ... if let m = model as? MessageContentModel { c.setModel(m, m.message?.isSelf ?? false) c.setSelect(m, isMutilSelect) } return c } else if let c = cell as? NEChatBaseCell, let m = model as? MessageContentModel { c.setModel(m, m.message?.isSelf ?? false) return cell } else { return NEBaseChatMessageCell() } }
子类继承
协同版皮肤下,P2PChatViewController 和 TeamChatViewController 均继承于 NormalChatViewController,NormalChatViewController 则继承于 ChatViewController
这里以 P2PChatViewController 为例,具体如下:
swift@objcMembers
open class P2PChatViewController: NormalChatViewController {
/// 重写父类的构造方法
/// - Parameter conversationId: 会话 ID
/// - Parameter anchor: 锚点消息
public init(conversationId: String, anchor: V2NIMMessage?) {
super.init(conversationId: conversationId)
viewModel = P2PChatViewModel(conversationId: conversationId, anchor: anchor)
}
...
}
swift@objcMembers
open class NormalChatViewController: ChatViewController {
override public init(conversationId: String) {
super.init(conversationId: conversationId)
navigationView.backgroundColor = .white
navigationController?.navigationBar.backgroundColor = .white
cellRegisterDic = ChatMessageHelper.getChatCellRegisterDic(isFun: false)
topMessageView.topImageView.image = UIImage.ne_imageNamed(name: "top_message_image")
topMessageView.layer.borderColor = UIColor(hexString: "#E8EAED").cgColor
topMessageView.layer.borderWidth = 1
}
...
}
通过源码自定义 UI
布局中的内容概略如下:
swift//这里以 ChatMessageTextCell 为例。
open class ChatMessageTextCell: NormalChatMessageBaseCell {
override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonUI()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
open func commonUI() {
bubbleImageLeft.addSubview(contentLabelLeft)
NSLayoutConstraint.activate([
contentLabelLeft.rightAnchor.constraint(equalTo: bubbleImageLeft.rightAnchor, constant: -chat_content_margin),
contentLabelLeft.leftAnchor.constraint(equalTo: bubbleImageLeft.leftAnchor, constant: chat_content_margin),
contentLabelLeft.topAnchor.constraint(equalTo: bubbleImageLeft.topAnchor, constant: chat_content_margin),
contentLabelLeft.bottomAnchor.constraint(equalTo: bubbleImageLeft.bottomAnchor, constant: -chat_content_margin),
])
bubbleImageRight.addSubview(contentLabelRight)
NSLayoutConstraint.activate([
contentLabelRight.rightAnchor.constraint(equalTo: bubbleImageRight.rightAnchor, constant: -chat_content_margin),
contentLabelRight.leftAnchor.constraint(equalTo: bubbleImageRight.leftAnchor, constant: chat_content_margin),
contentLabelRight.topAnchor.constraint(equalTo: bubbleImageRight.topAnchor, constant: chat_content_margin),
contentLabelRight.bottomAnchor.constraint(equalTo: bubbleImageRight.bottomAnchor, constant: -chat_content_margin),
])
}
override open func showLeftOrRight(showRight: Bool) {
super.showLeftOrRight(showRight: showRight)
contentLabelLeft.isHidden = showRight
contentLabelRight.isHidden = !showRight
}
override open func setModel(_ model: MessageContentModel, _ isSend: Bool) {
super.setModel(model, isSend)
let contentLabel = isSend ? contentLabelRight : contentLabelLeft
if let m = model as? MessageTextModel {
contentLabel.attributedText = m.attributeStr
contentLabel.accessibilityValue = m.message?.text
}
}
}
自定义标题栏按钮
自定义导航栏需要使用 navigationView 来实现。
navigationView 采用固定按钮,包括左侧的返回按钮(backButton)和右侧的更多按钮(moreButton),此外还包含中间的导航栏标题(navTitle)和底部分割线(titleBarBottomLine),具体定义请参考 NENavigationView 类,所有控件均可访问修改。
-
示例代码
SwiftSwift// 修改标题 navigationView.navTitle.text = "title" // 修改右侧按钮文案 navigationView.setMoreButtonTitle("clear") // 修改右侧按钮文案颜色 navigationView.moreButton.setTitleColor(.red, for: .normal) // 修改右侧按钮单击事件 navigationView.moreButton.addTarget(self, action: #selector(clearMessage), for: .touchUpInside)Objective-CObjective-C// 修改标题 navigationView.navTitle.text = @"title"; // 修改右侧按钮文案 [navigationView setMoreButtonTitle:@"clear"]; // 修改右侧按钮文案颜色 [navigationView.moreButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; // 修改右侧按钮单击事件 [navigationView.moreButton addTarget:self action:@selector(clearMessage) forControlEvents:UIControlEventTouchUpInside]; -
效果对比
默认 自定义后 

自定义标题栏标题
-
示例代码(Swfit)
swiftclass CustomP2PChatViewController: P2PChatViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } // 自定义标题 override func getSessionInfo(sessionId: String, _ completion: @escaping () -> Void) { super.getSessionInfo(sessionId: sessionId) { self.title = "小易助手" completion() } } }网易云信 IM 默认会在数据加载完成后设置会话消息页面标题,如需自定义标题,请重写
getSessionInfo方法,并在父类方法调用之后设置其值。 -
效果对比
默认 自定义后 

会话消息标题栏下方扩展视图
可在标题栏和消息详情中间插入扩展视图,例如插入 温馨提示。
-
示例代码(Swfit)
swiftclass CustomP2PChatViewController: P2PChatViewController { public lazy var customTopView: CustomView = { let view = CustomView(frame: CGRect(x: 0, y: 10, width: NEConstant.screenWidth, height: 40)) view.backgroundColor = .blue return view }() override func viewDidLoad() { super.viewDidLoad() ... // 聊天页顶部导航栏下方扩展视图示例 customTopView.btn.setTitle( **通过重写方式添加** , for: .normal) bodyTopView.addSubview(customTopView) bodyTopView.backgroundColor = .yellow bodyTopViewHeight = 80 } }示例代码中
bodyTopView为聊天页顶部标题栏下方预留的扩展视图,bodyTopViewHeight为该扩展视图的高度,更改此变量会刷新页面布局。 -
效果对比
默认 自定义后 

输入框上方区域扩展视图
用户可继承聊天界面详情页(P2PChatViewController/TeamChatViewController),在父类渲染页面之前更改输入框上方区域。
-
示例代码(Swfit)
swiftclass CustomP2PChatViewController: P2PChatViewController { override func viewDidLoad() { super.viewDidLoad() ... // 输入框上区域扩展视图示例 customBottomView.btn.setTitle("通过重写方式添加", for: .normal) bodyBottomView.addSubview(customBottomView) bodyBottomView.backgroundColor = .yellow bodyBottomViewHeight = 60 } }示例代码中
bodyBottomView为输入框上方预留的扩展视图,bodyBottomViewHeight为该扩展视图的高度,更改此变量会刷新页面布局。 -
效果对比
默认 自定义后 

自定义输入框左右间距
用户可自定义输入框左右间距,从而实现在输入框左右添加控件。
-
示例代码(Swfit)
swiftclass CustomP2PChatViewController: P2PChatViewController { override func viewDidLoad() { super.viewDidLoad() ... // 聊天页输入框左右间距自定义 chatInputView.textviewLeftConstraint?.constant = 100 chatInputView.textviewRightConstraint?.constant = -100 } }示例代码中
textviewLeftConstraint/textviewRightConstraint表示输入框左侧/右侧距页面左边框/右边框的距离,即输入框左右间距,更改此变量会刷新页面布局。 -
效果对比
默认 自定义后 

自定义底部工具条
用户可自定义输入框底部工具条的按钮样式和单击事件。
-
示例代码(Swfit)
swift// 通过配置项实现 class CustomP2PChatViewController: P2PChatViewController { override func viewDidLoad() { /// 文本输入框下方 tab 按钮定制 ChatUIConfig.shared.ui.chatInputBar = { [weak self] item in // 修改 let takePicBtn = item[2] takePicBtn.setImage(nil, for: .normal) takePicBtn.setTitle("拍照", for: .normal) takePicBtn.setTitleColor(.blue, for: .normal) takePicBtn.removeTarget(takePicBtn.superview, action: nil, for: .allEvents) takePicBtn.addTarget(self, action: #selector(self?.customClick), for: .touchUpInside) // 新增 let button = UIButton(type: .custom) button.setTitle("新增", for: .normal) button.setTitleColor(.blue, for: .normal) button.addTarget(self, action: #selector(self?.customClick), for: .touchUpInside) item.append(button) } ... super.viewDidLoad() } }swift// 通过重写实现 class CustomP2PChatViewController: P2PChatViewController { override func viewDidLoad() { super.viewDidLoad() ... let subviews = chatInputView.stackView.subviews subviews.forEach { view in view.removeFromSuperview() chatInputView.stackView.removeArrangedSubview(view) } let titles = ["表情", "语音", "照片", "更多"] for i in 0 ..< titles.count { let button = UIButton(type: .custom) let title = titles[i] button.translatesAutoresizingMaskIntoConstraints = false button.addTarget(self, action: #selector(buttonEvent), for: .touchUpInside) button.tag = i button.setTitle(title, for: .normal) button.setTitleColor(.blue, for: .normal) chatInputView.stackView.addArrangedSubview(button) } } @objc func buttonEvent(_ btn: UIButton) { if btn.tag == 0 { // 表情 layoutInputView(offset: bottomExanpndHeight, true) chatInputView.addEmojiView() } else if btn.tag == 1 { // 语音 layoutInputView(offset: bottomExanpndHeight, true) chatInputView.addRecordView() } else if btn.tag == 2 { // 照片 goPhotoAlbumWithVideo(self) } else if btn.tag == 3 { // 更多 layoutInputView(offset: bottomExanpndHeight, true) chatInputView.addMoreActionView() } } }示例代码中,
chatInputView为输入框视图,chatInputView.stackView为输入框底部工具栏视图,代码先将工具栏视图中的所有子视图(按钮)移除,然后依次添加自定义的子视图(按钮),并为按钮绑定单击事件buttonEvent。buttonEvent中利用tag对按钮进行区分,实现具体的逻辑。 -
效果对比
默认 自定义后 

自定义“更多”功能页
用户可根据业务需求,自定义聊天输入区域的 更多 功能区。
-
示例代码(Swfit)
swift// 通过配置项实现 class CustomP2PChatViewController: P2PChatViewController { /// 更多区域功能列表自定义示例 ChatUIConfig.shared.ui.chatInputMenu = { [weak self] menuList in // 新增未知类型 let itemNew = NEMoreItemModel() itemNew.customImage = UIImage(named: "mine_collection") itemNew.customDelegate = self itemNew.action = #selector(self?.customClick) itemNew.title = "新增" menuList.append(itemNew) // 覆盖已有类型 // 遍历 menuList,根据 type 覆盖已有类型 for item in menuList { if item.type == .rtc { item.customImage = UIImage(named: "mine_setting") item.customDelegate = self item.action = #selector(self?.customClick) item.type = .rtc item.title = "覆盖" } } // 移除已有类型 // 遍历 menuList,根据 type 移除已有类型 for (i, item) in menuList.enumerated() { if item.type == .file { menuList.remove(at: i) } } } ... // 需要在父类视图加载之前完成自定义 super.viewDidLoad() }swift// 通过重写方式实现 class CustomP2PChatViewController: P2PChatViewController { override func viewDidLoad() { /// **更多** 区域功能列表自定义示例 // 新增未知类型 let itemNew = NEMoreItemModel() itemNew.customImage = UIImage(named: "mine_collection") itemNew.customDelegate = self itemNew.action = #selector(customClick) itemNew.title = "新增" NEChatUIKitClient.instance.moreAction.append(itemNew) // 覆盖已有类型 // 遍历 NEChatUIKitClient.instance.moreAction,根据 type 覆盖已有类型 for (i, item) in NEChatUIKitClient.instance.moreAction.enumerated() { if item.type == .rtc { let itemReplace = NEChatUIKitClient.instance.moreAction[i] itemReplace.customImage = UIImage(named: "mine_setting") itemReplace.customDelegate = self itemReplace.action = #selector(customClick) itemReplace.type = .rtc itemReplace.title = "覆盖" } } // 移除已有类型 // 遍历 NEChatUIKitClient.instance.moreAction,根据 type 移除已有类型 for (i, item) in NEChatUIKitClient.instance.moreAction.enumerated() { if item.type == .file { NEChatUIKitClient.instance.moreAction.remove(at: i) } } ... // 需要在父类视图加载之前完成自定义 super.viewDidLoad() } }上述代码中的
NEChatUIKitClient.instance.moreAction为全局 更多 功能列表,通过itemNew.customImage和itemNew.customDelegate共同组合来实现按钮的自定义单击事件。IM UIKit 默认实现的能力如下表所示,内部对这些类型的单击事件做了默认实现,可根据业务需要来决定是否覆盖。标识 Action说明 NEMoreActionType.takePicture拍照 NEMoreActionType.file发送文件 NEMoreActionType.rtc音视频通话 NEMoreActionType.location地理位置 -
效果对比
默认 自定义后 

消息长按菜单过滤部分能力
长按消息功能弹窗可设置全局过滤列表或者针对单个功能项进行修改。
-
示例代码
swiftclass CustomP2PChatViewController: P2PChatViewController { override func viewDidLoad() { ... // 长按消息功能弹窗过滤列表(过滤列表中的能力会在整个页面中禁用) operationCellFilter = [.delete] } // 长按消息功能弹窗列表自定义(可针对不同 type 消息自定义长按功能项) override func setOperationItems(items: inout [OperationItem], model: MessageContentModel?) { if model?.type == .rtcCallRecord { items.append(OperationItem.replayItem()) } } } -
代码解读
-
示例代码中的
operationCellFilter为长按菜单过滤列表,其中的类型在整个会话详情页不会显示,最终的效果表现为:整个会话详情页的所有消息类型的长按菜单中均不显示 删除 功能。 -
示例代码中的
setOperationItems方法会返回每条消息的长按功能弹窗列表,可针对单条消息或者某种类型的消息进行长按功能项的自定义
-
-
效果对比
默认 自定义后 

消息长按弹出菜单自定义
-
示例代码
swiftclass CustomP2PChatViewController: P2PChatViewController { override func viewDidLoad() { ... /// 消息长按弹出菜单自定义 ChatUIConfig.shared.ui.chatPopMenu = { menuList, model in // 遍历 menuList,根据 type 覆盖已有类型 // 将所有文本消息的 **复制** 替换成 **粘贴** if model?.type == .text { for item in menuList { if item.type == .copy { item.text = "粘贴" } } } } /// 消息长按弹出菜单的单击事件回调,根据按钮类型进行区分 ChatUIConfig.shared.ui.popMenuClick = { [weak self] item in switch item.type { case .copy: // 更改 **复制** 类型按钮的单击事件 self?.customClick() default: break } } } } -
代码解读
示例代码中的
chatPopMenu为消息长按弹出菜单回调、popMenuClick为消息长按弹出菜单的单击事件回调,可以根据type类型进行区分进而实现自定义。 -
效果对比
默认 自定义后 






