Search code examples
swiftswift3avfoundationios10photosframework

Crash when using PHCachingImageManager().requestAVAsset


I am using PHCachingImageManager().requestAVAsset to load some videos from the camera roll:

 override func viewDidLoad() {
    super.viewDidLoad()
    print("SEGUE SUCCESSFUL")
    view.backgroundColor = .black
    avPlayerLayer = AVPlayerLayer(player: avPlayer)
    view.layer.insertSublayer(avPlayerLayer, at: 0)

    var asset2:AVAsset? = nil

    PHCachingImageManager().requestAVAsset(forVideo: (vidLocation?[videoSender]!)!, options: nil, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable : Any]?) in
        asset2 = asset! as AVAsset
        })


    let playerItem = AVPlayerItem(asset: asset2!)
    avPlayer.replaceCurrentItem(with: playerItem)
}

However when I run the program it pauses at the PHCachingImageManager().requestAVAsset line and shows:

THREAD 1 : EXC_BREAKPOINT

(highlighted in green)

I'm not sure what is happening and can't find anything I understand in the documentation. How do I fix this?


Solution

  • There are a couple of things you're going to want to do here in order to get this working.

    1. You need to cache the PHCachingImageManager as a property on an object that will stay alive. If you just create it without storing it somewhere, ARC rules will cause it to be thrown away. In the code below, I use a lazy var, but that's not the only way to do it.
    2. You should eliminate all forced unwrapped optionals ! in your code. Using the guard let ... or if let ... patterns may feel like more typing but it will save you a lot of time and frustration in the end. Think of the ! as a danger sign that says "CRASH HERE!".
    3. You need to set up the AVPlayerItem from the resultHandler completion block. requestAVAsset is asynchronous so that it doesn't block your main thread while it does the potentially expensive work of retrieving your asset. Basically, as soon as you call requestAVAsset a separate thread goes and does work for you, while the main thread continues working on the rest of the code in the viewDidLoad method. When it has successfully retrieved the AVAsset it calls back to the block of code you provided originally (on the main thread) so you can continue processing.

    Here's your code rewritten to incorporate the changes I'm suggesting:

    lazy var imageManager = {
        return PHCachingImageManager()
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("SEGUE SUCCESSFUL")
        view.backgroundColor = .black
        avPlayerLayer = AVPlayerLayer(player: avPlayer)
        view.layer.insertSublayer(avPlayerLayer, at: 0)
    
        var asset2:AVAsset? = nil
    guard let phAsset = vidLocation?[videoSender] else { return } //No video
    
    imageManager.requestAVAsset(forVideo: phAsset, options: nil, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable : Any]?) in
        if let avAsset = asset {
            self.play(asset: avAsset)
        }
    })
    
    func play(asset: AVAsset) {
        let playerItem = AVPlayerItem(asset: asset)
        avPlayer.replaceCurrentItem(with: playerItem)
    }
    

    Let me know if anything is unclear.