如何在不自定义整套控制UI的前提下为AVPlayer添加自定义按钮
AVPlayerViewController 原生控制栏添加自定义按钮尺寸异常解决方案
问题描述
- 需求:在不重新实现整套播放器控制UI的前提下,给原生AVPlayer控制栏添加自定义的视频画质调整入口,避免重复造轮子
- 参考实现效果:苹果开发者官方App的播放器控制栏自带额外功能按钮,经对比原生AVPlayer默认UI样式,可确认官方App是在原生AVPlayer基础上扩展控件,未完全自研控制UI

- 原生AVPlayer默认样式对比如下:

- 当前问题:尝试通过遍历子视图的方式往控制栏添加自定义按钮时,按钮无法正常调整尺寸,异常效果如下:

- 当前已尝试的实现逻辑:遍历AVPlayerViewController的子视图层级找到移动端辅助控制栏容器,将承载SwiftUI自定义视图的SelfSizingHostingController添加为子控制器,再重新排布控制栏内控件的约束,代码如下:
// uiViewController -> AVPlayerViewController // self.child -> A SwiftUI view let childViewController = SelfSizingHostingController(rootView: self.child) childViewController.view.translatesAutoresizingMaskIntoConstraints = false childViewController.view.contentMode = .scaleAspectFit if let avMobileAuxiliaryControlsView = uiViewController.view.subviews.first?.subviews[1].subviews[4], let parentVC = avMobileAuxiliaryControlsView.parentViewController { if !avMobileAuxiliaryControlsView.subviews.map({ String(describing: type(of: $0.self)) }).contains("AVControlOverflowButton") { return } uiViewController.buttonIsAdded = true // Remove all constraints avMobileAuxiliaryControlsView.removeConstraints(avMobileAuxiliaryControlsView.constraints) // Save subviews in dictionary for constraints(withVisualFormat:) var viewDictionary: [String: UIView] = [:] for view in avMobileAuxiliaryControlsView.subviews { let className = String(describing: type(of: view.self)) viewDictionary[className] = view; } viewDictionary["QualitySelectorView"] = childViewController.view; print("dict: \(viewDictionary.keys.joined(separator: ", "))") // Add to view parentVC.addChild(childViewController) avMobileAuxiliaryControlsView.addSubview(childViewController.view) childViewController.didMove(toParent: parentVC) // Update constraints avMobileAuxiliaryControlsView.addConstraints( NSLayoutConstraint.constraints(withVisualFormat: "H:|\(viewDictionary.keys.map({ "[\($0)]" }).joined(separator: "-"))", options: [], metrics: nil, views: viewDictionary)) }
解决方案
问题根因
当前实现存在三个核心问题直接导致按钮尺寸异常:
- 直接移除控制栏所有原有约束,破坏了原生控件自身的尺寸约束、控制栏的整体布局规则,会导致所有原生控件和自定义控件的布局失准
- 硬编码子视图下标(
subviews.first?.subviews[1].subviews[4])、用类名作为VFL约束的视图标识,在不同iOS系统版本下极易出现层级错位、类名不匹配的问题,直接导致约束失效 - 未给承载SwiftUI视图的控制器明确指定尺寸,仅靠内容自适应无法匹配AVPlayer控制栏的固定高度布局规则
方案1:公开API实现(优先推荐,无审核风险)
从iOS 16开始,苹果正式开放了AVPlayerViewController控制栏自定义的公开API,完全不需要遍历私有视图层级,系统会自动处理所有布局适配,不会出现尺寸异常问题:
- 直接使用
AVPlayerViewController的customInfoViewControllers属性,将承载自定义UI的控制器传入即可,系统会自动将控件添加到控制栏右侧的辅助控制区域,和原生控件样式、布局完全一致 - 实现代码:
// 初始化承载SwiftUI画质选择视图的控制器 let qualitySelectorVC = UIHostingController(rootView: QualitySelectorView()) // 明确指定控件的显示尺寸,匹配控制栏44pt的标准控件高度 qualitySelectorVC.preferredContentSize = CGSize(width: 44, height: 44) // 将自定义控制器添加到播放器控制栏 playerViewController.customInfoViewControllers = [qualitySelectorVC]
该方案是官方支持的实现方式,不存在系统版本升级失效、App Store审核拒审的风险,是所有方案中的首选。
方案2:低版本兼容实现(iOS 16以下适用)
如果需要兼容iOS 16以下的系统版本,可以使用视图遍历的方式实现,但必须注意不要破坏原生控件的原有约束:
- 不要硬编码子视图下标,通过递归遍历的方式匹配控制栏视图类名,避免不同系统版本层级错位
- 绝对不要删除控制栏的原有约束,只需要为自定义按钮追加布局约束即可
- 必须给自定义按钮设置明确的宽高约束,同时给HostingController设置
preferredContentSize,不要依赖内容自适应
- 核心实现代码片段:
// 递归查找辅助控制栏视图 func findAuxiliaryControlsView(in view: UIView) -> UIView? { let className = String(describing: type(of: view)) if className == "AVMobileAuxiliaryControlsView" { return view } for subview in view.subviews { if let target = findAuxiliaryControlsView(in: subview) { return target } } return nil } // 添加自定义按钮 guard let controlsView = findAuxiliaryControlsView(in: playerViewController.view), let overflowButton = controlsView.subviews.first(where: { String(describing: type(of: $0)) == "AVControlOverflowButton" }) else { return } let qualityVC = SelfSizingHostingController(rootView: QualitySelectorView()) qualityVC.preferredContentSize = CGSize(width: 44, height: 44) qualityVC.view.translatesAutoresizingMaskIntoConstraints = false playerViewController.addChild(qualityVC) controlsView.addSubview(qualityVC.view) qualityVC.didMove(toParent: playerViewController) // 仅追加自定义控件的约束,不修改原有约束 NSLayoutConstraint.activate([ qualityVC.view.widthAnchor.constraint(equalToConstant: 44), qualityVC.view.heightAnchor.constraint(equalToConstant: 44), qualityVC.view.centerYAnchor.constraint(equalTo: controlsView.centerYAnchor), qualityVC.view.trailingAnchor.constraint(equalTo: overflowButton.leadingAnchor, constant: -8) ])
注意事项
- 所有遍历私有视图层级的实现都存在随iOS系统升级失效的可能,若不需要兼容iOS 15及以下版本,强烈建议直接使用公开API实现
- 自定义控件的尺寸建议统一使用44x44pt的标准控制栏控件尺寸,和原生控件保持视觉一致
内容的提问来源于stack exchange,提问作者Noim
相关产品推荐
相关产品推荐

