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

