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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:23:19