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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:43:11