WWDC notes

Introducing to Photos Framework

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.

Good Article from NSHistper

Model Data

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 :

  • Media type
  • Creation date
  • Location
  • Favorite

PHAssetCollection represents albums, moments and smart albums including :

  • type
  • title
  • start date and end date

PHCollectionList represents folder or moment year of collections or other collection lists

  • type
  • title
  • start date and end date

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

Transient Collections (temporary Collections)

  • Run time only
  • used for search results and user selection

Can be interchangeable with reqular collections

  • fetch contents
  • reuse the view controllers
//Create temporary album
PHAssetCollection.transientAssetCollectionWithAssets(assets, title:title)

Fetching model objects

  • fast and synchronous fetching
  • batch result
  • usage similar to NSArray
  • Support user actions
    • Favorite a photo
    • Add to an album
  • Read-only

Change model data

  • Changing assets via change request classes
  • create in a change request block
  • Async and perform out of process (i.e. invoke after current scope)

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))
    })
}

Creating New Model Objects

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))
    })
}

Handle model changes

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

Code segments from Apple Doc

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

Image and Video Content

Photos may be in different size and video may be need to retrieve from remote location.

PHImageManager eases the asset retrieval.

  • request images based on the target sizes
  • request videos based on usage
  • Async API
  • Can ask to retrieve data from network

Retrieve image/video content

image retrieve

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
}

video retrieve

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

Edit content

  • Editing photo is in-place, there is no need for saving the editing
  • original copy is kept.
  • Editing changes can be viewed everywhere
  • Synced in iCloud

getting input

asset.requestContentEditingInputWithOptions(options) {
(editingInput, info) in  
    // handling asset input
}

making output

 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
...

Photo UI

  • extension for editing photo and video
  • UIViewController subclass
    • not suggested embedding navigation bar as the bar is provided for completion and canceling
  • conform protocol PHContentEditingController