页面结构示意图
一、准备工作
在开始调整开播页界面前,请先参考 主播开播 完成主流程的搭建。
二、功能概览
AnchorView 提供的核心自定义接口与属性
| 方法/属性 | 描述 |
| topRightItems | 用于灵活配置直播间右上角的按钮集合,支持自由添加自定义按钮或调整内置按钮的布局。 |
| bottomItems | 用于灵活配置直播间底部的按钮集合,支持自由添加自定义按钮或调整内置按钮的布局。 |
| replace(node:with:) | 用于将指定位置的默认组件(如顶部信息区、底部操作栏),替换为开发者自定义的全新视图。 |
| overlayView | 专属挂件图层,方便开发者自由添加需要在视频画面上方悬浮展示的全局业务 UI。 |
| perform(action:) | 用于在自定义视图中直接触发内置默认逻辑,例如显示默认观众列表,显示默认连麦管理面板等。 |
三、快速开始
下面示例快速搭建一个秀场主播工作台:
·底部栏添加美颜按钮。
·替换直播信息节点为自己业务风格的样式。
·添加“人气榜”悬浮活动挂件。
import UIKitimport AtomicXlet anchorView: AnchorViewfunc setupUI() { // 步骤1:创建美颜按钮,配置按钮顺序 let beautyButton = UIButton(type: .system) beautyButton.setTitle("美颜", for: .normal) // 依次排列:主播连麦、PK、自定义美颜按钮、更多 anchorView.bottomItems = [.coHost, .battle, .custom(beautyButton), .more] // 步骤2:调整直播间信息视图 LiveInfo 样式 let customLiveInfoView = CustomLiveInfoView() anchorView.replace(node: .liveInfo, with: customLiveInfoView) // 步骤3:添加“人气榜”浮层挂件 let rankWidget = RankWidgetView() // 将挂件视图添加到专属的挂件图层 anchorView.overlayView.addSubview(rankWidget) // 设置布局约束,贴靠在左上角下方 rankWidget.snp.makeConstraints { make in make.top.equalToSuperview().offset(120) make.leading.equalToSuperview().offset(12) make.width.equalTo(70) make.height.equalTo(30) }}
四、调整底部操作按钮
底部工具栏是主播进行互动操作的核心区域。目前从左到右默认显示“主播 PK”、“连观众”、“更多”三个按钮,开发者可以通过 .custom(UIView) 插入自定义按钮,或通过 bottomItems 属性灵活增删内置功能、调整按钮顺序。
五、实现步骤
步骤1:准备自定义按钮视图。按需创建自定义按钮视图对象。
步骤2:更新底部按钮数组。组装包含 AnchorBottomItem 枚举的数组,并重新赋值给组件属性。
import UIKitimport AtomicXlet anchorView: AnchorView// 示例场景:在底部工具栏仅保留连麦功能,并新增一个商品列表触发按钮func adjustBottomBar() { // 步骤1:准备自定义按钮视图 let goodsButton = UIButton(type: .custom) goodsButton.setImage(UIImage(named: "shop_cart"), for: .normal) // 步骤2:更新底部按钮数组 anchorView.bottomItems = [ .coHost, .custom(goodsButton) ]}
六、调整顶部操作按钮
顶部区域通常用于展示关键的房间信息和操作。目前从左到右默认显示“观众人数”、“悬浮窗”、“关闭”三个按钮,开发者可以通过 topRightItems 属性精简或增加顶部右上角的控制按钮。
七、实现步骤
·步骤 1:准备自定义按钮视图。按需创建自定义按钮视图对象。
·步骤 2:更新顶部按钮数组。将选定的枚举项或包装好的自定义视图赋值给 topRightItems 属性。
import UIKitimport AtomicXlet anchorView: AnchorView// 示例场景:在右上角保留观众人数、关闭房间按钮,新增一个“举报”按钮func adjustTopRightBar() { // 步骤1:准备自定义按钮视图 let reportButton = UIButton(type: .custom) reportButton.setImage(UIImage(named: "report_btn"), for: .normal) // 步骤2: 根据显示顺序更新顶部按钮数组 anchorView.topRightItems = [.audienceCount, .custom(reportButton), .close]}
八、替换界面指定区域视图
当调整按钮无法满足结构性修改需求(例如把页面左下角的弹幕输入框改成其他按钮)时,可使用 replace 接口整体替换指定区域的视图。
组件内部通过 AnchorNode 枚举定义了 5 个支持自定义替换的 UI 区域,可以结合开头的“页面结构示意图”理解:
| AnchorNode | 说明 |
| liveInfo | 左上角的主播与房间信息展示区域。 |
| topRightButtons | 右上角的系统控制按钮区域。 |
| networkInfo | 网络状态指示区域。 |
| bottomRightBar | 右下角的业务操作栏区域。 |
| barrageInput | 左下角的弹幕输入框触发区域。 |
注意:说明:
注意:replace 方法会完全替换指定节点,所以如果你替换了 topRightButtons 和 bottomRightBar 两个节点后,对应的 Items 属性会失效。建议你仅需调整按钮时使用前文介绍的 Items 属性,需要完全重构该区域布局时才使用 replace。
九、布局规则
replace 接口会将自定义视图放入指定的 slot 区域。视图的位置由框架控制,开发者无需设置。视图的尺寸由其自身决定,推荐使用以下两种方式之一声明尺寸:
十、方式 1:使用内部约束链(推荐)
确保子视图的约束形成完整链条,从而自动撑开父视图。
class MyInfoView: UIView { init() { super.init(frame: .zero) let label = UILabel() label.text = "直播中" addSubview(label) label.snp.makeConstraints { make in make.edges.equalToSuperview().inset(12) //由子视图撑开视图 } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }}
十一、方式 2:重写 intrinsicContentSize
通过重写属性直接指定视图的固定尺寸。
class MyInfoView: UIView { override var intrinsicContentSize: CGSize { CGSize(width: 200, height: 44) }}
十二、实现步骤
·步骤 1:创建自定义视图对象。
·步骤 2:调用 AnchorView 组件的 replace 接口,传入需要替换的节点枚举和新建的自定义视图。
let anchorView: AnchorView// 示例场景:使用自定义的直播信息视图替换默认区域func replaceLiveInfoNode() { // 步骤1:初始化符合布局规则的自定义视图 let customInfoView = MyInfoView() customInfoView.backgroundColor = .darkGray // 步骤2:调用替换接口更新特定节点 anchorView.replace(node: .liveInfo, with: customInfoView)}
十三、绑定事件与触发逻辑
当开发者使用自定义视图替换了默认按钮或局部节点后,需要自行接管该视图的交互事件。在事件响应方法中,开发者可以执行专属业务逻辑,也可以使用 perform 方法快速触发 TUILiveKit 内部的默认逻辑。
十四、实现步骤
·步骤 1:为自定义视图绑定事件,使用 addTarget 或手势识别器为视图添加点击事件。
·步骤 2:触发内置逻辑或执行业务代码,在事件回调中调用 perform 方法传入 AnchorAction 枚举,或执行其他业务代码。
import UIKitimport AtomicXclass YourAnchorViewController { var anchorView: AnchorView? // 示例场景:为自定义的直播信息视图绑定点击事件,并复用 SDK 内置的主播信息面板 func setupCustomLiveInfoNode() { // 步骤1:初始化自定义视图并绑定手势事件 let customLiveInfoView = MyInfoView() // 自定义的直播信息view customLiveInfoView.backgroundColor = .darkGray // 开启用户交互并添加点击手势 customLiveInfoView.isUserInteractionEnabled = true let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleLiveInfoClick)) customLiveInfoView.addGestureRecognizer(tapGesture) // 将自定义视图替换到直播信息节点 anchorView?.replace(node: .liveInfo, with: customLiveInfoView) } // 步骤2:触发内置逻辑或业务代码 @objc func handleLiveInfoClick() { // UI 节点视图自定义,但点击逻辑依然复用内置的主播信息弹窗 anchorView?.perform(.showLiveInfo) // 或是弹出你自己实现的主播信息弹窗 }}
十五、深度定制业务弹窗
当使用 perform 触发的内置默认面板无法满足具体的业务需求时,开发者可以彻底接管这部分逻辑,基于底层数据 Core SDK 构建全新的业务面板。
十六、核心思路
开发者可使用底层数据接口 AtomicXCore 获取房间、用户及状态数据,完全自主地完成自定义视图的搭建与交互绑定。在完成自定义控制器的构建后,建议使用内部封装的 AtomicPopover 组件将其弹出,以确保你的自定义面板也能获得与 SDK 内置面板一致的丝滑手势拦截与平滑动画。
十七、实现步骤
·步骤 1:构建自定义业务视图。创建一个独立的 UIView,并在其内部引入 AtomicXCore 接口,用于拉取或监听底层业务数据以驱动 UI 更新。
·步骤 2:配置弹窗容器参数。实例化弹窗配置对象,并指定弹出位置、占用高度与动画类型。
·步骤 3:弹出自定义视图。实例化你的自定义视图,将其作为 contentView 传入 AtomicPopover 容器,并通过系统方法进行展示。
import UIKitimport AtomicXimport AtomicXCore // 引入底层数据接口进行业务开发// 步骤1:构建自定义观众列表视图class CustomAudienceListView: UIView { override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .white setupUI() bindLiveData() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupUI() { // 在此添加你的自定义 UI 控件,例如展示观众头像、用户等级等 } private func bindLiveData() { // 使用 AtomicXCore 提供的核心接口获取当前房间状态或用户数据 // 拿到核心数据后刷新上方构建的自定义 UI }}// 示例场景:从屏幕底部向上滑出一个高度占屏幕一半的完全自定义面板func presentBusinessPanel(from parentViewController: UIViewController) { // 步骤2:配置弹窗容器参数 let config = AtomicPopover.AtomicPopoverConfig( position: .bottom, height: .ratio(0.5), animation: .slideFromBottom ) // 步骤3:弹出自定义视图 let audienceListView = CustomAudienceListView() let popover = AtomicPopover(contentView: audienceListView, configuration: config) parentViewController.present(popover, animated: true)}
请参考以下文档,使用 AtomicXCore 接口实现自定义功能面板页
| 功能描述 | 参考文档 |
| 实现观众连线管理面板:连麦申请 / 邀请 / 同意 / 拒绝,连麦成员权限控制(麦克风 / 摄像头),状态同步。 | 观众连线 |
| 实现主播跨房连线面板:连线主播互动管理,发起 / 接受 / 拒绝连线。 | 主播连线和 PK |
| 实现观众列表:统计观众数量,监听观众进出事件。 | 观众列表 |
| 实现音频特效面板:变声(童声 / 男声)、混响(KTV 等)、耳返调节,实时切换特效。 | 音效 |
十八、添加自定义悬浮挂件
复杂的直播场景常需要在视频画面上方悬浮展示活动图标或互动贴纸。这类需要定位于视频层之上且独立于基础布局的视图,要添加到 overlayView 图层中。
在实际业务中,悬浮挂件通常作为某个活动面板的入口。你可以将其与上一节介绍的 AtomicPopover 结合使用:给挂件绑定点击事件,并在点击后弹出自定义业务弹窗。
十九、实现步骤
·步骤 1:创建挂件视图并开启交互。实例化悬浮控件,设置其尺寸与位置。
·步骤 2:绑定点击事件。为挂件添加手势识别器,用于响应主播的点击操作。
·步骤 3:添加至覆盖图层并联动弹窗。将挂件添加至 overlayView 中,并在点击回调中调用前文定义的自定义弹窗逻辑。
import UIKitimport TUILiveKitclass LiveRoomController: UIViewController { var anchorView: AnchorView? // 示例场景:在画面左上角悬浮显示红包挂件,点击后联动弹出前文定义的业务面板 func addRedPacketWidget() { // 步骤 1:创建挂件视图并开启交互 let redPacketWidget = UIImageView(image: UIImage(named: "red_packet_icon")) redPacketWidget.frame = CGRect(x: 15, y: 120, width: 60, height: 60) redPacketWidget.isUserInteractionEnabled = true // 步骤 2:绑定点击事件 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleRedPacketClick)) redPacketWidget.addGestureRecognizer(tapGesture) // 步骤 3:添加至覆盖图层 anchorView?.overlayView.addSubview(redPacketWidget) } @objc func handleRedPacketClick() { // 在此处调用前文写好的 presentBusinessPanel 方法,弹出深度定制的业务视图 // presentBusinessPanel(from: self) }}
21、添加的自定义按钮为什么点击无响应?
使用 .custom() 或者 replace 传入自定义视图后,点击视图却无法触发绑定的事件。
排查建议:
·检查视图尺寸与内部约束(高频原因):这是最容易被忽略的问题。如果你传入了一个自定义的容器视图(例如替换了整个 bottomRightBar),请务必确保内部子控件形成了完整的约束链以撑开父视图,或者为主视图重写了 intrinsicContentSize。如果父视图实际尺寸为 0,即使内部按钮在屏幕上可见,点击事件也会因超出父视图边界而被系统直接丢弃。
·检查交互属性:检查 isUserInteractionEnabled 属性。如果是 UIImageView 或普通的 UIView 容器,系统默认该属性为 false,必须手动将其设置为 true。
·检查事件绑定与目标(Target):确认是否正确添加了 UITapGestureRecognizer 或调用了 addTarget 方法。
22、动态隐藏或显示操作按钮?
在实际业务中,可能需要根据主播的等级或房间状态(如 PK 阶段隐藏连麦按钮),动态调整底部或顶部的工具栏。
实现方案:AnchorView 的 bottomItems 和 topRightItems 属性是支持响应式更新的。只需组装一个新的按钮数组并重新赋值给该属性,SDK 内部便会自动触发视图刷新,无需手动调用 reloadData 等重绘方法。
23、替换视图后出现尺寸异常?
主视图内部的子控件需形成完整的约束链,或重写 intrinsicContentSize(详见上文的“自定义视图布局规则”章节)。