SwiftUI中调整视频尺寸时控件消失问题求助
问题描述
在ScrollView中嵌入基础视频播放器后,为维持视频原始宽高比手动获取并应用了宽高比设置,但针对部分窄宽高比(如竖屏视频)的视频,静音、全屏等系统控件会被裁切,无法正常显示,需要确保控件始终可见。


用户实现代码:
struct HustleVideoPlayerR: View { let url: URL @State var player = AVPlayer(url: URL(string: "https://www.google.com")!) @State var value: Float = 0 @State var viewID = false @State var aspect: CGSize = CGSize(width: 16, height: 9) @State private var currentTime: Double = 0.0 @State private var totalLength: Double = 1.0 var body: some View { VideoPlayer(player: $player, showControls: true) .id(viewID) .aspectRatio(aspect, contentMode: .fit) .background(Color.black.edgesIgnoringSafeArea(.all)) .onAppear { player = AVPlayer(url: url) viewID.toggle() Task { do { if let final = try await getVideoResolution(url: url.absoluteString) { self.aspect = final } } catch { } } self.player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1.0, preferredTimescale: 1), queue: .main) { (_) in self.value = self.getSliderValue() if self.value == 1.0 { self.player.seek(to: CMTime(seconds: 0, preferredTimescale: 1)) self.player.play() } if let total = self.player.currentItem?.duration.seconds, totalLength == 1.0 && total > 0.0 { totalLength = total } } } .onDisappear { self.player.pause() player = AVPlayer(url: URL(string: "https://www.google.com")!) viewID.toggle() } } func getSliderValue() -> Float { if let total = self.player.currentItem?.duration.seconds, self.player.currentTime().seconds > 0.0, total > 0.0 { currentTime = self.player.currentTime().seconds return Float(currentTime / total) } else { return 0.0 } } } struct VideoPlayer : UIViewControllerRepresentable { @Binding var player : AVPlayer let showControls: Bool func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPlayer>) -> AVPlayerViewController { let controller = AVPlayerViewController() controller.player = player controller.showsPlaybackControls = showControls controller.allowsVideoFrameAnalysis = false return controller } func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<VideoPlayer>) { } }
解决方法
方法1:添加播放器最小尺寸限制
直接为播放器设置最小宽高,确保系统控件有足够的显示空间,修改HustleVideoPlayerR的body部分:
VideoPlayer(player: $player, showControls: true) .id(viewID) .aspectRatio(aspect, contentMode: .fit) .frame(minWidth: 280, minHeight: 180) // 根据控件实际尺寸调整数值 .background(Color.black.edgesIgnoringSafeArea(.all))
这种方式简单直接,通过强制播放器不小于某个尺寸,避免极端窄高比导致控件被裁切。
方法2:动态调整宽高比上限
当视频宽高比过窄时,限制其最大高度比例,避免播放器被拉伸过长。修改onAppear中的宽高比获取逻辑:
Task { do { if let final = try await getVideoResolution(url: url.absoluteString) { // 设置允许的最大竖屏比例(比如2.5:1,可根据需求调整) let maxVerticalRatio: CGFloat = 2.5 let videoRatio = final.width / final.height if videoRatio < 1/maxVerticalRatio { // 视频高度过高,调整为允许的最大比例 self.aspect = CGSize(width: 1, height: maxVerticalRatio) } else { self.aspect = final } } } catch { } }
该方法在尽可能保留视频原始比例的同时,避免播放器尺寸极端化,给控件留出显示空间。
方法3:自定义播放器控件
完全抛弃系统AVPlayerViewController的控件,自行实现播放、静音、全屏等按钮,彻底控制控件布局。示例简化实现:
struct CustomVideoPlayer: View { let url: URL @State private var player = AVPlayer() @State private var isPlaying = false @State private var aspect: CGSize = .init(width: 16, height: 9) var body: some View { ZStack { AVPlayerContainer(player: $player) .aspectRatio(aspect, contentMode: .fit) // 自定义底部控件栏 VStack { Spacer() HStack(spacing: 20) { // 播放/暂停按钮 Button(action: { isPlaying.toggle() isPlaying ? player.play() : player.pause() }) { Image(systemName: isPlaying ? "pause.fill" : "play.fill") .foregroundColor(.white) .font(.title) } Spacer() // 全屏按钮 Button(action: { // 这里实现全屏逻辑 }) { Image(systemName: "arrow.up.left.and.arrow.down.right") .foregroundColor(.white) .font(.title) } } .padding(.horizontal, 16) .padding(.vertical, 8) .background(Color.black.opacity(0.7)) } } .background(Color.black) .onAppear { player.replaceCurrentItem(with: AVPlayerItem(url: url)) // 原有获取视频宽高比逻辑 Task { if let final = try await getVideoResolution(url: url.absoluteString) { aspect = final } } } } } // 自定义AVPlayer容器 struct AVPlayerContainer: UIViewRepresentable { @Binding var player: AVPlayer func makeUIView(context: Context) -> UIView { let view = UIView() let playerLayer = AVPlayerLayer() playerLayer.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.player = player playerLayer.frame = uiView.bounds } }
这种方式最灵活,完全可控,不会出现系统控件被裁切的问题,但需要自行实现所有控件的交互逻辑。
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

