SwiftUI中如何按比例以最大宽度播放视频?
解决SwiftUI中视频自适应宽度并保持宽高比的问题
核心问题在于AVPlayerLayer本身没有固有尺寸,SwiftUI无法自动推导其布局,直接使用maxWidth/maxHeight: .infinity会触发布局循环导致崩溃。下面是可行的解决方案:
步骤1:封装AVPlayer的UIViewRepresentable视图
先创建一个适配SwiftUI的播放器视图,确保视频层能跟随父视图尺寸变化并保持宽高比:
import SwiftUI import AVKit struct PlayerView: UIViewRepresentable { let player: AVPlayer func makeUIView(context: Context) -> UIView { let view = UIView() let playerLayer = AVPlayerLayer(player: player) playerLayer.videoGravity = .resizeAspect // 强制保持视频宽高比 view.layer.addSublayer(playerLayer) return view } func updateUIView(_ uiView: UIView, context: Context) { guard let playerLayer = uiView.layer.sublayers?.first as? AVPlayerLayer else { return } playerLayer.frame = uiView.bounds // 让视频层完全填充父视图 } }
步骤2:获取视频真实宽高比
通过AVAsset异步加载视频轨道信息,获取视频原生宽高比,避免硬编码尺寸:
class VideoPlayerVM: ObservableObject { let player: AVPlayer @Published var aspectRatio: CGFloat = 16/9 // 默认16:9,加载后自动更新 init(videoURL: URL) { let asset = AVAsset(url: videoURL) self.player = AVPlayer(url: videoURL) // 异步加载视频轨道数据 asset.loadValuesAsynchronously(forKeys: ["tracks"]) { [weak self] in guard let self = self else { return } do { let status = try asset.statusOfValue(forKey: "tracks", error: nil) if status == .loaded, let videoTrack = asset.tracks(withMediaType: .video).first { let naturalSize = videoTrack.naturalSize.applying(videoTrack.preferredTransform) DispatchQueue.main.async { self.aspectRatio = abs(naturalSize.width / naturalSize.height) } } } catch { print("加载视频轨道失败: \(error.localizedDescription)") } } } }
步骤3:构建自适应布局的主视图
利用GeometryReader获取可用宽度,结合视频宽高比计算高度,实现视频占满最大可用宽度且保持比例:
struct VideoContentView: View { @StateObject private var vm: VideoPlayerVM init(videoURL: URL) { _vm = StateObject(wrappedValue: VideoPlayerVM(videoURL: videoURL)) } var body: some View { GeometryReader { geo in PlayerView(player: vm.player) .frame( maxWidth: .infinity, height: geo.size.width / vm.aspectRatio ) .clipped() // 防止视频内容溢出 } .onAppear { vm.player.play() } .onDisappear { vm.player.pause() } } }
额外优化
如果需要限制视频最大高度(比如竖屏视频避免超出屏幕),可以修改高度计算逻辑:
height: min(geo.size.width / vm.aspectRatio, geo.size.height)
内容的提问来源于stack exchange,提问作者Duck
相关产品推荐
相关产品推荐

