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

SwiftUI中AVPlayer旋转至竖屏时exitFullScreen未调用问题

问题现象

使用SwiftUI搭配AVPlayer开发播放功能时,设备旋转至横屏可正常触发播放器进入全屏,旋转回竖屏时exitFullScreen函数未执行,播放器无法自动退出全屏。

根因分析
  • 进入全屏后,AVPlayerViewController会被系统以present方式弹出到独立的全屏展示容器,原有SwiftUI视图树中挂载的onReceive旋转监听会因为视图层级变化失效,竖屏旋转事件无法触发fullScreen状态更新。
  • 原实现未做全屏状态幂等校验,updateUIViewController每次触发都会无条件调用全屏切换方法,重复调用私有进入全屏API会导致系统忽略后续的退出全屏指令。
  • 旋转监听注册在SwiftUI包装视图层,和实际负责全屏展示的AVPlayerViewController实例生命周期不同步,状态变更无法传递到已经弹出的播放器控制器。
修复实现

调整AVPlayer封装代码

新增Coordinator协调器统一处理旋转监听、状态同步,增加调用前置校验避免重复触发API:

import SwiftUI
import AVKit

struct AVPlayerControllerRepresentable: UIViewControllerRepresentable {
    @Binding var showFullScreen: Bool
    @Binding var player: AVPlayer
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let controller = AVPlayerViewController()
        controller.player = player
        controller.showsPlaybackControls = false
        // 将旋转监听注册在协调器上,生命周期和控制器绑定
        NotificationCenter.default.addObserver(
            context.coordinator,
            selector: #selector(Coordinator.handleOrientationChange),
            name: UIDevice.orientationDidChangeNotification,
            object: nil
        )
        context.coordinator.targetController = controller
        return controller
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
        // 仅在状态不匹配时触发全屏切换,避免重复调用
        guard context.coordinator.currentIsFullScreen != showFullScreen else { return }
        showFullScreen ? uiViewController.enterFullScreen(animated: true) : uiViewController.exitFullScreen(animated: true)
        context.coordinator.currentIsFullScreen = showFullScreen
    }
    
    class Coordinator: NSObject {
        let parent: AVPlayerControllerRepresentable
        weak var targetController: AVPlayerViewController?
        var currentIsFullScreen: Bool = false
        
        init(_ parent: AVPlayerControllerRepresentable) {
            self.parent = parent
        }
        
        @objc func handleOrientationChange() {
            DispatchQueue.main.async { [weak self] in
                guard let self = self, let controller = self.targetController else { return }
                let isLandscape = UIDevice.current.orientation.isLandscape
                guard self.currentIsFullScreen != isLandscape else { return }
                
                if isLandscape {
                    controller.enterFullScreen(animated: true)
                } else {
                    controller.exitFullScreen(animated: true)
                }
                self.currentIsFullScreen = isLandscape
                self.parent.showFullScreen = isLandscape
            }
        }
    }
}

extension AVPlayerViewController {
    func enterFullScreen(animated: Bool) {
        // 前置校验:当前控制器未处于present流程、未展示其他页面时才执行
        guard !isBeingPresented, presentedViewController == nil else { return }
        let selector = NSSelectorFromString("enterFullScreenAnimated:completionHandler:")
        guard responds(to: selector) else { return }
        perform(selector, with: animated, with: nil)
    }
    
    func exitFullScreen(animated: Bool) {
        let selector = NSSelectorFromString("exitFullScreenAnimated:completionHandler:")
        guard responds(to: selector) else { return }
        perform(selector, with: animated, with: nil)
    }
}

简化业务视图代码

移除原视图层挂载的旋转监听逻辑,避免重复触发状态变更:

VStack {
    AVPlayerControllerRepresentable(showFullScreen: $fullScreen, player: $player)
        .ignoresSafeArea()
        .frame(width: 290, height: 220)
        .overlay {
            BoxTv()
        }
        .opacity(1.0)
        .padding([.bottom, .top], 40)
}
.onAppear {
    player.play()
}
关键修复逻辑说明
  • 协调器和AVPlayerViewController生命周期绑定,旋转监听不会因为播放器弹出全屏而失效,竖屏旋转事件可以被正常捕获。
  • 增加currentIsFullScreen状态标记,所有全屏切换操作前先做状态比对,从根源避免重复调用私有API导致的系统指令忽略问题。
  • 给私有API调用增加方法响应校验、控制器展示状态校验,避免无效调用触发系统异常。
  • 控制器层触发全屏切换后主动回写SwiftUI绑定状态,保证业务层状态和播放器实际展示状态完全同步。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 12:03:17