Search code examples
swiftswiftuisingleton

"Unexpectedly found nil" while trying to initialize properties in a custom singleton class


For my SwiftUI app, I'm trying to create a singleton which will control an AVPlayer and AVPlayerLayer so that I can control video playback on each page of a TabView. I think I'm making a rather rudimentary error here but I can't quite understand the problem. I'm getting this error:

Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

from this line:

self.pipController = AVPictureInPictureController(playerLayer: self.playerLayer)

It seems like super.init() is interfering with my ability to assign properties in my init() but I'm not sure why. Can you explain?

The whole class:

import Foundation
import AVFoundation
import AVKit

// A singleton to control a globally available AVPlayer and AVPlayerLayer
@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()
        
        self.playerLayer.player = self.player
        self.pipController = AVPictureInPictureController(playerLayer: self.playerLayer)
        self.pipController.delegate = self
    }
    
    func setupPictureInPicture() {
        // Ensure PiP is supported by current device.
        if AVPictureInPictureController.isPictureInPictureSupported() {
            // Create a new controller, passing the reference to the AVPlayerLayer.
            

            pipPossibleObservation = pipController.observe(\AVPictureInPictureController.isPictureInPicturePossible,
    options: [.initial, .new]) { [weak self] _, change in
                // Update the PiP button's enabled state.
//                self?.pipButton.isEnabled = change.newValue ?? false
            }
        } else {
            // PiP isn't supported by the current device. Disable the PiP button.
//            pipButton.isEnabled = false
        }
    }
}


Solution

  • init(playerLayer:) is a failable initialiser. It will fail (evaluate to nil) when the device does not support picture in picture.

    Before attempting to create a controller instance, verify that the current device supports Picture in Picture by calling the isPictureInPictureSupported() class method. Attempting to create a Picture in Picture controller on an unsupported device returns nil.

    It is likely that you are running this on a Simulator, which doesn't support picture in picture, as far as I know. pipController is set to nil, so the access of pipController.delegate crashes. You can verify this by removing the pipController.delegate = self line. Xcode for some reason (probably an off-by-one bug) put the error message on the previous line. The error message printed in the console shows the correct line number.

    In any case, you should not make pipController an implicitly unwrapped optional, as it is to be expected that some devices do not support picture-in-picture, and you should handle that case appropriately.