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
相关产品推荐
相关产品推荐

