Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caching Live Photos #62

Closed
bryan1anderson opened this issue Apr 6, 2016 · 4 comments
Closed

Caching Live Photos #62

bryan1anderson opened this issue Apr 6, 2016 · 4 comments

Comments

@bryan1anderson
Copy link

Caching live photos that are downloaded actually requires storing these photos on the disk.

Nuke will be the most robust caching system I am aware of if its able to handle all this for you

@kean
Copy link
Owner

kean commented Apr 6, 2016

By default, Nuke relies only on HTTP caching implemented in Cocoa for on-disk caching. I've written a comprehensive guide on this topic.

If custom on-disk caching is required than take a look at ImageDiskCaching protocol:

public protocol ImageDiskCaching {
    func setData(data: NSData, response: NSURLResponse, forTask task: ImageTask)
    func dataFor(task: ImageTask) -> NSData?
    func removeAllCachedImages()
}

See CustomCacheDemoViewController for an example.

@kean
Copy link
Owner

kean commented Apr 6, 2016

I'm not aware of the details related to live photos. If there are some specific requirements please let me know.

@bryan1anderson
Copy link
Author

Sure! First of all I love Nuke and would prefer to use it for everything, this being my number one motivation for hoping a future build could handle PHLivePhoto and PHLivePhotoView.

Extremely possible ImageDiskCaching protocol may do the trick! I'll need to see. I'll list as many requirements for using PHLivePhotoView and PHLivePhoto and if you have time perhaps you could offer your best recommendation for integrating. Thanks

PHLivePhotoView is very similar to UIImageView. Where UIImageView has an optional image property, PHLivePhotoView has an optional livePhoto property.

The actual PHLivePhotoView is a very friendly object. Not overly complex, and very familiar.

However, PHLivePhoto is very different from its "counterpart" UIImage.

Obviously it is very easy to instantiate a PHLivePhoto from a UIImagePickerController:

func didFinishPickingImageWithInfo(info: [String: AnyObject]) {
if #available(iOS 9.1, *) {
   let livePhoto = info[UIImagePickerControllerLivePhoto] as? PHLivePhoto
}
}

And here is where it gets tricky. I use an imagePickerController to select a live photo, I use PHAssetResource to get the NSData chunks that make up a PHLivePhoto. I separate that data, upload it to my server.. So now somewhere in a cloud I have a .mov file, and a .jpeg file that constitute a live photo. There is a great tutorial explaining this process here: https://milen.me/writings/live-photo-ios-api-overview/

I now need to create a PHLivePhoto using the files on my server. Nuke is great at this. I could just use nk_setImageWith(url). Even UIImage makes it easy with an NSURL convenience init.. Granted not the best way....

The only method to instantiate a live photo from the .jpeg and .mov data is this:

class func requestLivePhotoWithResourceFileURLs(_ fileURLs: [NSURL],
                               placeholderImage image: UIImage?,
                                     targetSize targetSize: CGSize,
                                    contentMode contentMode: PHImageContentMode,
                                  resultHandler resultHandler: (PHLivePhoto?,
                                      [NSObject : AnyObject]) -> Void) -> PHLivePhotoRequestID

https://developer.apple.com/library/ios/documentation/Photos/Reference/PHLivePhoto_Class/index.html#//apple_ref/occ/clm/PHLivePhoto/requestLivePhotoWithResourceFileURLs:placeholderImage:targetSize:contentMode:resultHandler:

Where UIImage has convenience inits for NSData, and NSURL for remote files, PHLivePhoto only has this method. It only accepts two NSURL's (.mov and .jpeg files). These NSURL CANNOT point to a remote location, like on a server. This method will fail if I provide any NSURL that doesn't point the a file actually stored on the disk.
So I can't just download the data from the server. I have to download it, then store it to disk, then use I can use the NSURL's poiningt to those files on the disk.

So far the solution for me is:
Use NSURLSession to download and store the files.

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {

        // First we need to make sure that the folder in which we wish to place the file exists
        if let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first
        {
            let baseUrl = NSURL(fileURLWithPath:documentsDirectoryPath)
            livePhotoFolder = baseUrl.URLByAppendingPathComponent("LivePhotos")
            do {
                try NSFileManager.defaultManager().createDirectoryAtURL(livePhotoFolder, withIntermediateDirectories: true, attributes: nil)
            }
            catch {
                print(error)
            }
        }
        // Next we retrieve the suggested name of our file and this file name to the URL
        guard let suggestedName = downloadTask.response?.suggestedFilename else { return }
        let newLocation = livePhotoFolder.URLByAppendingPathComponent(suggestedName)
        do {
            try NSFileManager.defaultManager().moveItemAtURL(location, toURL: newLocation)
            let pathExtension = newLocation.pathExtension
            if pathExtension == "mov" {
                self.videoURL = newLocation
            } else {
                self.photoURL = newLocation
            }
        }
        catch {
            print(error)
        }
    }

Now self.videoURL and self.photoURL can be used to obtain the live photos.

I can now use requestLivePhotoWithResourceFileURLs to finally create my PHLivePhoto

        PHLivePhoto.requestLivePhotoWithResourceFileURLs([videoURL, photoURL], placeholderImage: nil, targetSize: CGSizeZero, contentMode: PHImageContentMode.AspectFit) {
            (livePhoto, infoDict) -> Void in
            print(infoDict)
            if let livePhoto = livePhoto {
                   //YAY, do what you want with it.. 
            }
        }

I'm in the process of building a simple caching system. End goal is to extend PHLivePhotoView to have similar functionality to nk_setImageWith(url)
so
let imageURL = NSURL(string: "https://pbs.twimg.com/media/1234_.jpg")
let movieURL = NSURL(string: "https://pbs.twimg.com/media/1234_.mov")

myLivePhotoView.setLivePhotoWith(imageURL: imageURL, movieURL: movieURL)
But I'm not very experienced when it comes to a caching algorithm that will intelligently delete these large files, and honestly it has been such a pain to get my live photos even working, I see good potential for Nuke to expand functionality for PHLivePhotos so no one ever needs to worry about this again.

@kean
Copy link
Owner

kean commented Apr 7, 2016

Thanks, I'll take a look later today. From the first impression, it does seem quiet complicated.

My first thought was to create a custom UIView subclass that would request .jpg and .mov via separate tasks (why make user wait for a .mov?). Nuke already supports .jpg. And I think @mitchellporter has already tricked Nuke into supporting .mov files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants