Starting from iOS 8, PhotoKit ( Photos and PhotoUI ) provides an access photos and videos of photo library, creating a full-feature app like built-in Photos app and editing app extension.
Object hicherchy is
collection list (like a year) 1 <-> m of asset collection(like an event collection) 1<-> m of assets (including images and videos)
PHAsset represents photos and videos including properties :
PHAssetCollection represents albums, moments and smart albums including :
PHCollectionList represents folder or moment year of collections or other collection lists
Contens of collections are not cached
//Fetching all video
PHAsset.fetchAssetsWithMediaType(.Video , options: nil)
//Fetching all moments
PHAssetCollection.fetchMomentsWithOptions(nil)
//Fetching assets in an asset collection
PHAsset.fetchAssetsInAssetCollection(myAlbum, options: nil)
//Options are used for sorting and filtering
Can be interchangeable with reqular collections
//Create temporary album
PHAssetCollection.transientAssetCollectionWithAssets(assets, title:title)
PHAssetChangeRequest for PHAsset
PHAssetCollectionChangeRequest for PHAssetCollection
PHCollectionListChangeRequest for PHCollectionList
setCreationDate(_) and setFavorite(_) for updating the models
Sample Code for submitting change request
func toggleFavoriteForAsset(asset: PHAsset) {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
// Create a change request from the asset to be modified.
let request = PHAssetChangeRequest(forAsset: asset)
// Set a property of the request to change the asset itself.
request.favorite = !asset.favorite
}, completionHandler: { success, error in
NSLog("Finished updating asset. %@", (success ? "Success." : error))
})
}
func saveImageToPhotosLibrary(image: UIImage!) {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
//create an add request with the image
let addAssetRequest = PHAssetChangeRequest.createRequestForAssetFromImage(image)
// get a placeholder for newly created asset which can reference later on
let assetPlaceHolder = request.placeholderForCreatedAsset
//putting the asset in the collection
let assetRequest = PHAssetCollectionChangeRequest(forAssetCollection: collection)
}, completionHandler: { success, error in
NSLog("Finished updating asset. %@", (success ? "Success." : error))
})
}
Photos and other application could gain assess to photo libraries and deliver changes to the library. iCloud also updates the library.
The change notification will be delivered to the app when the app did fetch photo library for once.
Conform PHPhotoLibraryChangeObserver for listening any changes in photo library
func photoLibraryDidChange(changeInfo: PHChange!) {
// Photos may call this method on a background queue;
// switch to the main queue to update the UI.
dispatch_async(dispatch_get_main_queue()) {
// Check for changes to the displayed album itself
// (its existence and metadata, not its member assets).
if let albumChanges = changeInfo.changeDetailsForObject(self.displayedAlbum) {
// Fetch the new album and update the UI accordingly.
self.displayedAlbum = albumChanges.objectAfterChanges as PHAssetCollection
self.navigationController.navigationItem.title = self.displayedAlbum.localizedTitle
}
// Check for changes to the list of assets (insertions, deletions, moves, or updates).
if let collectionChanges = changeInfo.changeDetailsForFetchResult(self.assetsFetchResults) {
// Get the new fetch result for future change tracking.
self.assetsFetchResults = collectionChanges.fetchResultAfterChanges
if collectionChanges.hasIncrementalChanges {
// Get the changes as lists of index paths for updating the UI.
var removedPaths: [NSIndexPath]?
var insertedPaths: [NSIndexPath]?
var changedPaths: [NSIndexPath]?
if let removed = collectionChanges.removedIndexes {
removedPaths = self.indexPathsFromIndexSet(removed)
}
if let inserted = collectionChanges.insertedIndexes {
insertedPaths = self.indexPathsFromIndexSet(inserted)
}
if let changed = collectionChanges.changedIndexes {
changedPaths = self.indexPathsFromIndexSet(changed)
}
// Tell the collection view to animate insertions/deletions/moves
// and to refresh any cells that have changed content.
self.collectionView.performBatchUpdates(
{
if removedPaths {
self.collectionView.deleteItemsAtIndexPaths(removedPaths)
}
if insertedPaths {
self.collectionView.insertItemsAtIndexPaths(insertedPaths)
}
if changedPaths {
self.collectionView.reloadItemsAtIndexPaths(changedPaths)
}
if (collectionChanges.hasMoves) {
collectionChanges.enumerateMovesWithBlock() { fromIndex, toIndex in
let fromIndexPath = NSIndexPath(forItem: fromIndex, inSection: 0)
let toIndexPath = NSIndexPath(forItem: toIndex, inSection: 0)
self.collectionView.moveItemAtIndexPath(fromIndexPath, toIndexPath: toIndexPath)
}
}
}, completion: nil)
} else {
// Detailed change information is not available;
// repopulate the UI from the current fetch result.
self.collectionView.reloadData()
}
}
}
}
Photos may be in different size and video may be need to retrieve from remote location.
PHImageManager eases the asset retrieval.
var manager = PHImageManager()
var options = PHImageRequestOptions()
options.networkAccessAllowed = true
options.progressHandler = { [unowned self] (progress, error, stop, [NSObject : AnyObject]!) -> Void in
//update UI for progress downloading
self.updateProgress(progress, error: error)
}
manager.requestImageForAsset(asset, targetSize: CGSize(width: 160, height: 160), contentMode: .AspectFill, options: options) { (image, [NSObject : AnyObject]!) -> Void in
//local library's asset and remote asset downloaded
//this closure maybe called multiple time
//low-res iCloud image return in 1st time
//high-res iCloud image deliver in later time
}
let videoManager = PHImageManager.defaultManager()
let videoOptions = PHVideoRequestOptions()
videoOptions.deliveryMode = .HighQualityFormat
videoOptions.networkAccessAllowed = true
videoOptions.progressHandler = { [unowned self] (progress, error, stop, [NSObject : AnyObject]!) -> Void in
self.updateProgress(progress, error: error)
}
videoManager.requestExportSessionForVideo(asset, options: videoOptions, exportPreset: "video") { (session, [NSObject : AnyObject]!) -> Void in
}
PHCachingImageManager is used for best scrolling performance as the manager caches the image in smart way.
// Use the same args as for requestImageForAsset
// size is the visible size when scrolling
// this call will do caching in background thread
var cachingImageManager = PHCachingImageManager()
cachingImageManager.startCachingImagesForAsset(asset, targetSize: CGSize(width: 320, height:480), contentMode: .AspectFill, options: nil)
//this call will stop caching on background thread
cachingImageManager.stopCachingImagesForAsset(asset, targetSize: CGSize(width: 320, height:480), contentMode: .AspectFill, options: nil)
//this call stop all asset caching
cachingImageManager.stopCachingForAllAssest()
asset.requestContentEditingInputWithOptions(options) {
(editingInput, info) in
// handling asset input
}
var asset = PHAsset()
var options = PHContentEditingInputRequestOptions()
asset.requestContentEditingInputWithOptions(options) {
(input, [NSObject : AnyObject]!) in
var output = PHContentEditingOutput(contentEditingInput: input)
var adjustmentData = PHAdjustmentData(formatIdentifier: "com.apple.", formatVersion: "1.0", data: NSData())
output.adjustmentData = adjustmentData
// changeRequest to save it
changeRequest.contentEditingOutput = output
}
since the asset will be given with the adjustment data, therefore it is important to find out if the adjustment data is understood.
If yes , iOS will deliver the original image and all adjustment data. We can then change all adjustment data.
If not, iOS will combines all changes to that original asset and deliver that final product to edit.
//use this to let the system knows if the app can handle
var options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { (adjustmentData) in
return adjustmentData.formatIdentifier == "com.apple" && adjustmentData.formatVersion == "1.0"
// can be more complex as needed e.g. unarchive the data and parse the information
...
PHContentEditingController