SwiftUI单例类初始化时遇Unexpectedly found nil错误求助
解决AVPictureInPictureController初始化时的隐式解包崩溃问题
崩溃原因分析
- AVPlayerLayer未关联UI层级:你在单例的
init方法里直接用刚创建的playerLayer初始化PiP控制器,但此时这个playerLayer还没有被添加到任何UIView的layer上。AVPictureInPictureController要求传入的playerLayer必须属于一个已挂载到视图层级的视图,否则初始化会返回nil,而你用了隐式解包!,直接触发崩溃。 - 未提前检查PiP支持性:如果当前设备/系统不支持画中画,
AVPictureInPictureController(playerLayer:)会返回nil,跳过检查直接创建必然导致解包失败。 - 初始化时机错误:在单例的
init里执行依赖UI状态的操作本身就不合适,此时UI环境可能还未准备完成。
修复方案
- 将pipController改为可选类型:去掉隐式解包
!,避免强制解包nil引发崩溃。 - 延迟创建PiP控制器:把PiP控制器的创建逻辑移到
setupPictureInPicture方法中,先检查设备支持性,再确保playerLayer已挂载到UI视图后再初始化。 - 确保playerLayer关联视图:调用
setupPictureInPicture之前,必须先将playerLayer添加到某个UIView的layer上。
修改后的代码
import Foundation import AVFoundation import AVKit @MainActor final class GlobalVideoPlayerController: NSObject, AVPictureInPictureControllerDelegate { let player: AVPlayer = AVPlayer() let playerLayer = AVPlayerLayer() var pipController: AVPictureInPictureController? // 改为可选类型 var pipPossibleObservation: NSKeyValueObservation? static let shared = GlobalVideoPlayerController() private override init() { super.init() playerLayer.player = player // 移除init中的PiP控制器创建逻辑 } func setupPictureInPicture() { guard AVPictureInPictureController.isPictureInPictureSupported() else { // 不支持PiP时的处理逻辑 return } // 确保playerLayer已添加到UI视图的layer上 guard playerLayer.superlayer != nil else { assertionFailure("playerLayer必须先添加到UI视图的layer上才能初始化PiP控制器") return } pipController = AVPictureInPictureController(playerLayer: playerLayer) pipController?.delegate = self pipPossibleObservation = pipController?.observe(\.isPictureInPicturePossible, options: [.initial, .new]) { [weak self] _, change in // 这里添加更新PiP按钮状态的逻辑 } } }
SwiftUI中使用的额外注意事项
需要通过UIViewRepresentable将playerLayer绑定到原生UIView上,示例如下:
struct VideoPlayerView: UIViewRepresentable { let playerController = GlobalVideoPlayerController.shared func makeUIView(context: Context) -> UIView { let view = UIView() playerController.playerLayer.frame = view.bounds playerController.playerLayer.videoGravity = .resizeAspect view.layer.addSublayer(playerController.playerLayer) // 可在此处或视频加载完成后调用setup方法 // playerController.setupPictureInPicture() return view } func updateUIView(_ uiView: UIView, context: Context) { playerController.playerLayer.frame = uiView.bounds } }
内容的提问来源于stack exchange,提问作者zakdances
相关产品推荐
相关产品推荐

