Search code examples
iosswiftuikitswift-playgroundphlivephoto

Working with Live Photos in Playground


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?


Solution

  • 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()