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

SwiftUI单例类初始化时遇Unexpectedly found nil错误求助

解决AVPictureInPictureController初始化时的隐式解包崩溃问题

崩溃原因分析

  • AVPlayerLayer未关联UI层级:你在单例的init方法里直接用刚创建的playerLayer初始化PiP控制器,但此时这个playerLayer还没有被添加到任何UIView的layer上。AVPictureInPictureController要求传入的playerLayer必须属于一个已挂载到视图层级的视图,否则初始化会返回nil,而你用了隐式解包!,直接触发崩溃。
  • 未提前检查PiP支持性:如果当前设备/系统不支持画中画,AVPictureInPictureController(playerLayer:)会返回nil,跳过检查直接创建必然导致解包失败。
  • 初始化时机错误:在单例的init里执行依赖UI状态的操作本身就不合适,此时UI环境可能还未准备完成。

修复方案

  1. 将pipController改为可选类型:去掉隐式解包!,避免强制解包nil引发崩溃。
  2. 延迟创建PiP控制器:把PiP控制器的创建逻辑移到setupPictureInPicture方法中,先检查设备支持性,再确保playerLayer已挂载到UI视图后再初始化。
  3. 确保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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 22:48:41