You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不自定义整套控制UI的前提下为AVPlayer添加自定义按钮

AVPlayerViewController 原生控制栏添加自定义按钮尺寸异常解决方案

问题描述

  • 需求:在不重新实现整套播放器控制UI的前提下,给原生AVPlayer控制栏添加自定义的视频画质调整入口,避免重复造轮子
  • 参考实现效果:苹果开发者官方App的播放器控制栏自带额外功能按钮,经对比原生AVPlayer默认UI样式,可确认官方App是在原生AVPlayer基础上扩展控件,未完全自研控制UI
    Apple Developer App播放器效果
  • 原生AVPlayer默认样式对比如下:
    个人实现的AVPlayerView默认效果
  • 当前问题:尝试通过遍历子视图的方式往控制栏添加自定义按钮时,按钮无法正常调整尺寸,异常效果如下:
    按钮尺寸异常效果
  • 当前已尝试的实现逻辑:遍历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))
}

解决方案

问题根因

当前实现存在三个核心问题直接导致按钮尺寸异常:

  1. 直接移除控制栏所有原有约束,破坏了原生控件自身的尺寸约束、控制栏的整体布局规则,会导致所有原生控件和自定义控件的布局失准
  2. 硬编码子视图下标(subviews.first?.subviews[1].subviews[4])、用类名作为VFL约束的视图标识,在不同iOS系统版本下极易出现层级错位、类名不匹配的问题,直接导致约束失效
  3. 未给承载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以下的系统版本,可以使用视图遍历的方式实现,但必须注意不要破坏原生控件的原有约束:

  1. 不要硬编码子视图下标,通过递归遍历的方式匹配控制栏视图类名,避免不同系统版本层级错位
  2. 绝对不要删除控制栏的原有约束,只需要为自定义按钮追加布局约束即可
  3. 必须给自定义按钮设置明确的宽高约束,同时给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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 11:48:27