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

SwiftUI横竖屏切换时避免重建UIViewControllerRepresentable的方案

解决方案

问题核心是:SwiftUI在切换不同类型的视图(LandscapeMediaPlayerView和PotraitMediaPlayerView)时,会销毁旧视图树并创建新实例,导致内部的SwiftUIPlayer及其底层MediaPlayerViewController、AVPlayer被重建,视频播放中断。

最优实现:复用播放器实例

将播放器的底层控制器实例提升到父视图层级管理,让横屏/竖屏视图共用同一个播放器实例,避免重建。具体步骤:

  1. 创建一个持有播放器控制器的包装类,用@StateObject保证其生命周期与父视图绑定:
class PlayerContainer: ObservableObject {
    let mediaPlayerVC: MediaPlayerViewController
    
    init() {
        mediaPlayerVC = MediaPlayerViewController()
        // 在这里初始化AVPlayer、加载视频等逻辑
    }
}
  1. 修改UIViewControllerRepresentable,接收外部传入的播放器控制器,而非内部创建:
struct ReusablePlayerView: UIViewControllerRepresentable {
    let playerVC: MediaPlayerViewController
    
    func makeUIViewController(context: Context) -> MediaPlayerViewController {
        playerVC
    }
    
    func updateUIViewController(_ uiViewController: MediaPlayerViewController, context: Context) {
        // 在这里根据横竖屏状态调整播放器UI(比如控制条位置、全屏模式等)
    }
}
  1. 父视图中持有播放器容器,横屏/竖屏视图复用同一个ReusablePlayerView实例:
struct PlayerHostView: View {
    @StateObject private var playerContainer = PlayerContainer()
    @Environment(\.horizontalSizeClass) private var sizeClass
    
    var body: some View {
        Group {
            if sizeClass == .compact {
                PortraitPlayerLayout(playerView: ReusablePlayerView(playerVC: playerContainer.mediaPlayerVC))
            } else {
                LandscapePlayerLayout(playerView: ReusablePlayerView(playerVC: playerContainer.mediaPlayerVC))
            }
        }
    }
}

// 竖屏布局视图
struct PortraitPlayerLayout: View {
    let playerView: ReusablePlayerView
    
    var body: some View {
        VStack(spacing: 0) {
            playerView
                .frame(height: 200)
            // 竖屏专属UI:标题、评论区等
            Text("视频标题")
                .padding()
            Spacer()
        }
    }
}

// 横屏布局视图
struct LandscapePlayerLayout: View {
    let playerView: ReusablePlayerView
    
    var body: some View {
        ZStack {
            playerView
                .ignoresSafeArea()
            // 横屏专属UI:悬浮控制条、进度条等
            VStack {
                HStack {
                    Button("返回") {}
                    Spacer()
                }
                .padding()
                Spacer()
            }
            .foregroundColor(.white)
        }
    }
}

这种方式下,横竖屏切换时只是调整布局容器,播放器控制器和AVPlayer实例完全不会被销毁,视频播放不会中断,同时保留了横屏/竖屏视图的代码拆分。

关于单例AVPlayer的问题

单例AVPlayer可以解决视频实例不被销毁的问题,但不推荐作为常规方案:

  • 单例是全局生命周期,即使当前页面不需要播放器了,实例也不会被释放,容易造成内存泄漏;
  • 如果APP中有多个播放器场景(比如列表页小窗播放、详情页全屏播放),单例会导致状态冲突(比如一个页面暂停播放,另一个页面的播放器也会暂停)。

只有当你的APP是全局唯一播放器(比如类似抖音的全屏短视频APP),且能严格控制单例的状态管理时,才考虑使用单例。否则,上述的视图层级复用实例的方案更安全、灵活。

内容的提问来源于stack exchange,提问作者Thomas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 19:53:45