Live Photo API on iOS
What are Live Photos? From a marketing perspective, Live Photos record the moments just before and after you take a photo. Under the hood, a Live Photo is just a JPEG image together with a QuickTime file contaning an H.264 track.
Storing Live Photo Data
The first step in using Live Photos is to retrieve them and then access the underlying data. The easiest way to get hold of a Live Photo is to ask the user to select one from their camera roll. For this, we can use UIImagePickerController
— the crucial part is including both kUTTypeLivePhoto
and kUTTypeImage
in the media types array.
@IBAction func showImagePicker(sender: UIButton) {
let picker = UIImagePickerController()
picker.delegate = self;
picker.allowsEditing = false;
picker.sourceType = .PhotoLibrary;
picker.mediaTypes = [kUTTypeLivePhoto as String, kUTTypeImage as String];
presentViewController(picker, animated: true, completion: nil);
}
Once the user has selected a photo, the delegate method will be called. From there we can check whether they have actually selected a Live Photo. If that’s the case, we will generate a random directory where we can store all the associated resources. The PHAssetResourceManager
gives us chunks of data which we concatenate. If we wanted to be more efficient, we could have used an NSOutputStream
or NSFileHandle
.
func imagePickerController(
picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [String : AnyObject]
) {
guard
let livePhoto = info[UIImagePickerControllerLivePhoto] as? PHLivePhoto,
let photoDir = generateFolderForLivePhotoResources()
else {
return;
}
let assetResources = PHAssetResource.assetResourcesForLivePhoto(livePhoto)
for resource in assetResources {
let buffer = NSMutableData()
PHAssetResourceManager.defaultManager().requestDataForAssetResource(
resource,
options: nil,
dataReceivedHandler: { (chunk: NSData) -> Void in
buffer.appendData(chunk)
},
completionHandler:saveAssetResource(resource, inDirectory: photoDir, buffer: buffer)
)
}
}
Once we finished collecting the data for a resource, we’re ready to write it to a file. We get the preferred file extension and save the file to disk.
func saveAssetResource(
resource: PHAssetResource,
inDirectory: NSURL,
buffer: NSMutableData)(maybeError: NSError?
) -> Void {
guard maybeError == nil else {
print("Could not request data for resource: \(resource), error: \(maybeError)")
return
}
let maybeExt = UTTypeCopyPreferredTagWithClass(
resource.uniformTypeIdentifier,
kUTTagClassFilenameExtension
)?.takeRetainedValue()
guard let ext = maybeExt else {
return
}
var fileUrl = inDirectory.URLByAppendingPathComponent(NSUUID().UUIDString)
fileUrl = fileUrl.URLByAppendingPathExtension(ext as String)
if(!buffer.writeToURL(fileUrl, atomically: true)) {
print("Could not save resource \(resource) to filepath \(fileUrl)")
}
}
Below, you can see how we generate a random directory. In the example code, it will be a subdirectory of the temporary directory, which gets cleaned up by the OS at unspecified intervals.
func generateFolderForLivePhotoResources() -> NSURL? {
let photoDir = NSURL(
// NB: Files in NSTemporaryDirectory() are automatically cleaned up by the OS
fileURLWithPath: NSTemporaryDirectory(),
isDirectory: true
).URLByAppendingPathComponent(NSUUID().UUIDString)
let fileManager = NSFileManager()
// we need to specify type as ()? as otherwise the compiler generates a warning
let success : ()? = try? fileManager.createDirectoryAtURL(
photoDir,
withIntermediateDirectories: true,
attributes: nil
)
return success != nil ? photoDir : nil
}
After you have saved all the resources to disk, you can transfer them to your servers or any other destination.
Displaying Live Photos
To go in the other direction, all we need is a list of file NSURL
s representing a single Live Photo. Those would have been downloaded by your app from your servers. Note that the block callback will be executed multiple times, initially providing a PHLivePhoto
of degraded quality.
func showLivePhoto(photoFiles: [NSURL]) {
let requestID = PHLivePhoto.requestLivePhotoWithResourceFileURLs(
photoFiles,
placeholderImage: nil,
targetSize: CGSizeZero,
contentMode: .AspectFit
) { (livePhoto: PHLivePhoto?, info: [NSObject : AnyObject]) -> Void in
let error = info[PHLivePhotoInfoErrorKey] as? NSError
let degraded = info[PHLivePhotoInfoIsDegradedKey] as? NSNumber
let cancelled = info[PHLivePhotoInfoCancelledKey] as? NSNumber
let finished = (
(error != nil) ||
(cancelled != nil && cancelled!.boolValue == true) ||
(degraded != nil && degraded!.boolValue == false)
)
if finished {
// If you have saved `requestID`, you can now set it to PHLivePhotoRequestIDInvalid
print("Live photo request finished")
}
if let photo = livePhoto where photo.size == CGSizeZero {
// Workaround for crasher rdar://24021574 (https://openradar.appspot.com/24021574)
return;
}
self.photoView.livePhoto = livePhoto
}
}