Working with Live Photos in Playground
Asked Answered
F

1

19

I've done a fair amount of searching the web, but I'm currently attempting to work with "Live Photos" in Playground. I'm aware of the framework (PHLivePhoto), I just have no clue if working with them in Playground is possible due to that fact that there's not much to "import" as there doesn't seem to be any "Live Photos" available for download online. Any ideas?

Fleur answered 30/11, 2015 at 3:2 Comment(0)
C
30

It's possible to make and view a PHLivePhoto in the Playground from the elements of an actual Live Photo.

In OS X's Photos app, select a Live Photo and go to menu

File > Export > Export original...

enter image description here

It will create a .JPG and a .mov.

Drop these two files in the Resources folder of the Playground (menu View > Navigators > Show Project Navigator).

Get the URLs for these two files with NSBundle (in my example the files are "IMG_0001.JPG" and "IMG_0001.mov"):

let imgURL = NSBundle.mainBundle().URLForResource("IMG_0001", withExtension: "JPG")!
let movURL = NSBundle.mainBundle().URLForResource("IMG_0001", withExtension: "mov")!

And create an actual image, we will need it for the Live Photo preview image:

let prevImg = UIImage(named: "IMG_0001.JPG")!

Import the necessary frameworks:

import Photos
import PhotosUI
import XCPlayground

And set the Playground in asynchronous mode:

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

Now we're going to use PHLivePhoto's requestLivePhotoWithResourceFileURLs method to create a PHLivePhoto from our elements:

func makeLivePhotoFromItems(imageURL: NSURL, videoURL: NSURL, previewImage: UIImage, completion: (livePhoto: PHLivePhoto) -> Void) {
    PHLivePhoto.requestLivePhotoWithResourceFileURLs([imageURL, videoURL], placeholderImage: previewImage, targetSize: CGSizeZero, contentMode: PHImageContentMode.AspectFit) {
        (livePhoto, infoDict) -> Void in
        // for debugging: print(infoDict)
        if let lp = livePhoto {
            completion(livePhoto: lp)
        }
    }
}

Then we call like this:

makeLivePhotoFromItems(imgURL, videoURL: movURL, previewImage: prevImg) { (livePhoto) -> Void in
    // "livePhoto" is your PHLivePhoto object
}

For example, let's say you want the Playground to make a live view:

makeLivePhotoFromItems(imgURL, videoURL: movURL, previewImage: prevImg) { (livePhoto) -> Void in
    let rect = CGRect(x: 0, y: 0, width: 2048, height: 1536)
    let livePhotoView = PHLivePhotoView(frame: rect)
    livePhotoView.livePhoto = livePhoto
    XCPlaygroundPage.currentPage.liveView = livePhotoView
    livePhotoView.startPlaybackWithStyle(PHLivePhotoViewPlaybackStyle.Full)
}

Note that since there's no way to interact with the live view to start the playback of the Live Photo we have to do it ourselves with PHLivePhotoView's startPlaybackWithStyle method.

You can force the live view to appear in the Playground by showing the Assistant Editor in menu

View > Assistant Editor > Show Assistant Editor

enter image description here

Note: it can take some time for the Playground to create the PHLivePhoto and initiate the live view.


With Xcode 7.3b+ we can finally have some UI interaction in Playgrounds.

I've made an adaptation of this answer with a simple view and touchesBegan, just click the LivePhoto when the console says so:

import UIKit
import XCPlayground

import Photos
import PhotosUI

class PLView: UIView {

    let image: UIImage
    let imageURL: NSURL
    let videoURL: NSURL

    let liveView: PHLivePhotoView

    init(image: UIImage, imageURL: NSURL, videoURL: NSURL) {
        self.image = image
        self.imageURL = imageURL
        self.videoURL = videoURL
        let rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
        self.liveView = PHLivePhotoView(frame: rect)
        super.init(frame: rect)
        self.addSubview(self.liveView)
    }

    func prepareLivePhoto() {
        makeLivePhotoFromItems { (livePhoto) in
            self.liveView.livePhoto = livePhoto
            print("\nReady! Click on the LivePhoto in the Assistant Editor panel!\n")
        }
    }

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        print("\nClicked! Wait for it...\n")
        self.liveView.startPlaybackWithStyle(.Full)
    }

    private func makeLivePhotoFromItems(completion: (PHLivePhoto) -> Void) {
        PHLivePhoto.requestLivePhotoWithResourceFileURLs([imageURL, videoURL], placeholderImage: image, targetSize: CGSizeZero, contentMode: .AspectFit) {
            (livePhoto, infoDict) -> Void in
            // This "canceled" condition is just to avoid redundant passes in the Playground preview panel.
            if let canceled = infoDict[PHLivePhotoInfoCancelledKey] as? Int where canceled == 0 {
                if let livePhoto = livePhoto {
                    completion(livePhoto)
                }
            }
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}



XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

let plview = PLView(image: UIImage(named: "IMG_0001.JPG")!,
                 imageURL: NSBundle.mainBundle().URLForResource("IMG_0001", withExtension: "JPG")!,
                 videoURL: NSBundle.mainBundle().URLForResource("IMG_0001", withExtension: "mov")!)

XCPlaygroundPage.currentPage.liveView = plview

plview.prepareLivePhoto()

The same example for Swift 3.0.2 (Xcode 8.2.1):

import UIKit
import PlaygroundSupport
import Photos
import PhotosUI

class PLView: UIView {

    let image: UIImage
    let imageURL: URL
    let videoURL: URL

    let liveView: PHLivePhotoView

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    init(image: UIImage, imageURL: URL, videoURL: URL) {
        self.image = image
        self.imageURL = imageURL
        self.videoURL = videoURL
        let rect = CGRect(x: 0, y: 0, width: 300, height: 400)
        self.liveView = PHLivePhotoView(frame: rect)
        super.init(frame: rect)
        self.addSubview(self.liveView)
    }

    func prepareLivePhoto() {
        makeLivePhotoFromItems { (livePhoto) in
            self.liveView.livePhoto = livePhoto
            print("\nReady! Click on the LivePhoto in the Assistant Editor panel!\n")
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("\nClicked! Wait for it...\n")
        self.liveView.startPlayback(with: .full)
    }

    private func makeLivePhotoFromItems(completion: @escaping (PHLivePhoto) -> Void) {
        PHLivePhoto.request(withResourceFileURLs: [imageURL, videoURL], placeholderImage: image, targetSize: CGSize.zero, contentMode: .aspectFit) {
            (livePhoto, infoDict) -> Void in

            if let canceled = infoDict[PHLivePhotoInfoCancelledKey] as? NSNumber,
                canceled == 0,
                let livePhoto = livePhoto
            {
                completion(livePhoto)
            }
        }
    }

}

let plview = PLView(image: UIImage(named: "IMG_0001.JPG")!,
                    imageURL: Bundle.main.url(forResource: "IMG_0001", withExtension: "JPG")!,
                    videoURL: Bundle.main.url(forResource: "IMG_0001", withExtension: "mov")!)

PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = plview

plview.prepareLivePhoto()
Californium answered 5/12, 2015 at 17:43 Comment(5)
It works pretty well, a couple seconds to load and a bit of lag, but otherwise its all good :)Fleur
this is definitely one of my favorite answers on stack overflow, though code may not working with Swift 3.0.2, seemed Apple make some changes with live photo(?Petitioner
@XueYu Ah, thanks. ^^ I've made an update, now it works: I had to change Int to NSNumber for the PHLivePhotoInfoCancelledKey value, and to reduce the size of the preview to speed up the whole thing.Californium
Hey, seems this will work when the mov and jpg were exported from an actual livephoto, but seems there may be some metadata issues when creating a livephoto with arbitrary mov and jpgEfficient
Hmm, I am aware of that, trying to find a way to hack around to add metadata to the jpg and mov file now, there is a swift version of doing that by reading both the files and writing to disk again with metadata, which I think is kind of silly, and I can't find the way to inspect that metadata in image preview's inspector either, maybe there is a way to writing a bash script to add that metadata?Efficient

© 2022 - 2024 — McMap. All rights reserved.