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

SwiftUI中实现横屏视频裁剪右侧并充满屏幕高度作为背景的方法

SwiftUI中实现横屏视频裁剪右侧并充满屏幕高度作为背景的方法

我完全懂你遇到的困扰——AVPlayerLayer默认的适配逻辑确实有点“轴”,要么把视频居中显示,要么强制适配宽/高,想实现「填满屏幕高度、保持原比例、只裁剪右侧」这种自定义布局,确实得绕开它的默认行为。下面我就结合你的思路,给你完善出可行的方案:

核心思路

要实现这个需求,我们需要手动控制视频层的尺寸和位置:

  • 先获取视频的原始宽高比,计算出“填满屏幕高度”时视频需要的宽度
  • 把视频层的高度设为屏幕高度,宽度按比例计算
  • 让视频层的左边缘和屏幕左边缘对齐,这样当视频宽度超过屏幕宽度时,右侧超出的部分就会被自动裁剪

完整实现代码

import SwiftUI
import AVFoundation

class CroppedVideoViewModel: ObservableObject {
    @Published var videoSize: CGSize = .zero
    
    func loadVideo(url: URL) {
        let asset = AVAsset(url: url)
        let track = asset.tracks(withMediaType: .video).first
        // 注意:这里要考虑视频的旋转方向,避免尺寸获取错误
        let naturalSize = track?.naturalSize ?? .zero
        let transform = track?.preferredTransform ?? .identity
        let isRotated = transform.a == 0 && transform.d == 0
        self.videoSize = isRotated ? CGSize(width: naturalSize.height, height: naturalSize.width) : naturalSize
    }
}

struct ContentView: View {
    @StateObject var viewModel = CroppedVideoViewModel()
    private let screenBounds = UIScreen.main.bounds
    
    var body: some View {
        ZStack {
            if viewModel.videoSize != .zero {
                let videoAspectRatio = viewModel.videoSize.width / viewModel.videoSize.height
                // 计算填满屏幕高度所需的视频宽度
                let requiredVideoWidth = screenBounds.height * videoAspectRatio
                
                CroppedVideoRepresentable(viewModel: viewModel)
                    .frame(width: requiredVideoWidth, height: screenBounds.height)
                    .alignmentGuide(.leading) { _ in 0 } // 让视频左边缘和屏幕左边缘对齐
                    .clipped() // 裁剪超出屏幕的部分
            }
        }
        .ignoresSafeArea() // 忽略安全区,让视频铺满整个屏幕
        .onAppear {
            guard let url = Bundle.main.url(forResource: "example", withExtension: "MP4") else {
                print("Video not found!")
                return
            }
            viewModel.loadVideo(url: url)
        }
    }
}

struct CroppedVideoRepresentable: UIViewRepresentable {
    @ObservedObject var viewModel: CroppedVideoViewModel
    
    func makeUIView(context: Context) -> UIView {
        CroppedVideo(viewModel: viewModel)
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {}
}

class CroppedVideo: UIView {
    @ObservedObject var viewModel: CroppedVideoViewModel
    private var player: AVPlayer?
    private var videoLayer: AVPlayerLayer!
    
    init(viewModel: CroppedVideoViewModel) {
        self.viewModel = viewModel
        super.init(frame: .zero)
        backgroundColor = .black
        
        setupVideoLayer()
        loadAndPlayVideo()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupVideoLayer() {
        videoLayer = AVPlayerLayer()
        videoLayer.videoGravity = .resizeAspect // 保持视频原比例,不拉伸
        layer.addSublayer(videoLayer)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        // 让视频层铺满整个UIView的 bounds
        videoLayer.frame = bounds
    }
    
    private func loadAndPlayVideo() {
        guard let url = Bundle.main.url(forResource: "example", withExtension: "MP4") else { return }
        player = AVPlayer(url: url)
        videoLayer.player = player
        player?.play()
        // 循环播放(可选)
        NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
            self?.player?.seek(to: .zero)
            self?.player?.play()
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

关键细节说明

  • 修正视频尺寸获取:原代码没有考虑视频的旋转方向,比如有些横屏视频可能被标记为竖屏+旋转,这时候直接取naturalSize会得到错误的宽高比,所以我们通过preferredTransform判断旋转状态,修正视频尺寸。
  • 自定义布局对齐:使用alignmentGuide(.leading)让视频视图的左边缘和屏幕左边缘对齐,这样当视频宽度超过屏幕宽度时,只有右侧会被裁剪,符合你的需求。
  • 视频层自适应布局:把videoLayer的frame设置为UIView的bounds,这样当SwiftUI的frame更新时,视频层会自动适配,不用手动计算。
  • 循环播放(可选):添加了通知监听视频播放结束,自动循环播放,如果你不需要可以删掉这部分代码。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 10:45:28