Build Great Photo Experiences in iOS 8 with PhotoKit

Mike Bluestein

PhotoKit is a new framework in iOS 8 that allows you to query the system image library and create custom user interfaces to view and modify its contents. To use PhotoKit from Xamarin.iOS, you can download the preview release in our alpha channel.

PhotoKit1

PhotoKit includes a number of classes that represent image and video assets, as well as collections of assets such as albums and folders. Collectively, PhotoKit represents these in what it calls model objects.

The model objects that represents the photos and videos themselves are of type PHAsset. A PHAsset contains metadata such as the asset’s media type and its creation date.

Similarly, the PHAssetCollection and PHCollectionList classes contain metadata about asset collections and collection lists respectively. Asset collections are groups of assets, such as all the photos and videos for a given year. Likewise, collection lists are groups of asset collections, such as photos and videos grouped by year.

PhotoKit makes it easy to query model data through a variety of fetch methods. For example, to retrieve all images, you would call PFAsset.Fetch, passing the PHAssetMediaType.Image media type.

PHFetchResult fetchResults = PHAsset.FetchAssets (PHAssetMediaType.Image, null);

The PHFetchResult instance would then contain all the PFAsset instances representing images. To get the images themselves, you use the PHImageManager (or the caching version, PHCachingImageManager) to make a request for the image by calling RequestImageForAsset. For example, the following code retrieves an image for each asset in a PHFetchResult to display in a collection view cell:

public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
{
  var imageCell = (ImageCell)collectionView.DequeueReusableCell (cellId, indexPath);

  imageMgr.RequestImageForAsset ((PHAsset)fetchResults [(uint)indexPath.Item], thumbnailSize,
    PHImageContentMode.AspectFill, new PHImageRequestOptions (), (img, info) => {
      imageCell.ImageView.Image = img;
    });

    return imageCell;
}

This results in a grid of images as shown below:

PhotoKit1

That’s how to handle querying and reading data. You can also write changes back to the library. Since multiple interested applications are able to interact with the system photo library, you can register an observer to be notified of changes using a PhotoLibraryObserver. Then, when changes come in, your application can update accordingly. For example, here’s a simple implementation to reload the collection view above:

class PhotoLibraryObserver : PHPhotoLibraryChangeObserver
{
  readonly PhotosViewController controller;

  public PhotoLibraryObserver (PhotosViewController controller)
  {
    this.controller = controller;
  }

  public override void PhotoLibraryDidChange (PHChange changeInstance)
  {
    DispatchQueue.MainQueue.DispatchAsync (() => {

      var changes = changeInstance.GetFetchResultChangeDetails (controller.fetchResults);
      controller.fetchResults = changes.FetchResultAfterChanges;
      controller.CollectionView.ReloadData ();
    });
  }
}

To actually write changes back from your application, you create a change request. Each of the model classes has an associated change request class. For example, to change a PHAsset, you create a PHAssetChangeRequest. The steps to perform changes that are written back to the photo library and sent to observers like the one above are:

  1. Perform the editing operation.
  2. Save the filtered image data to a PHContentEditingOutput instance.
  3. Make a change request to publish the changes form the editing output.

Here’s an example that writes back a change to an image that applies a core image noir filter:

void ApplyNoirFilter (object sender, EventArgs e)
{
  Asset.RequestContentEditingInput (new PHContentEditingInputRequestOptions (), (input, options) => {
    //
    // perform the editing operation, which applies a noir filter in this case
    var image = CIImage.FromUrl (input.FullSizeImageUrl);
    image = image.CreateWithOrientation ((CIImageOrientation)input.FullSizeImageOrientation);
    var noir = new CIPhotoEffectNoir {
      Image = image
    };
    var ciContext = CIContext.FromOptions (null);
    var output = noir.OutputImage;

    var uiImage = UIImage.FromImage (ciContext.CreateCGImage (output, output.Extent));
    imageView.Image = uiImage;
    //
    // save the filtered image data to a PHContentEditingOutput instance
    var editingOutput = new PHContentEditingOutput(input);
    var adjustmentData = new PHAdjustmentData();
    var data = uiImage.AsJPEG();
    NSError error;
    data.Save(editingOutput.RenderedContentUrl, false, out error);
    editingOutput.AdjustmentData = adjustmentData;
    //
    // make a change request to publish the changes form the editing output
    PHPhotoLibrary.GetSharedPhotoLibrary.PerformChanges (
      () => {
        PHAssetChangeRequest request = PHAssetChangeRequest.ChangeRequest(Asset);
        request.ContentEditingOutput = editingOutput;
      },
      (ok, err) => Console.WriteLine ("photo updated successfully: {0}", ok));
  });
}

When the users selects the button, the filter is applied:

PhotoKit2

And thanks to the PHPhotoLibraryChangeObserver, the change is reflected in the collection view when the user navigates back:

PhotoKit3

PhotoKit is a welcome addition to iOS that allows greater flexibility for incorporating photo library data into applications. It opens the doors for third party developers to build more integrated photo and video experiences than ever before.

The code from this post is available in my GitHub repo.

Discuss this blog post in the Xamarin Forums

 

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Michał Jan 0

    Hi, as of now (beginning of Aug 2019) this sample doesn’t seem to work (when run on current versions of SDKs, libraries, etc.). Upon calling PerformChanges() – its completion handler allowing to access the result shows that ok variable is false, and the err varialble contains vague {The operation couldn’t be completed. (Cocoa error -1.)}. Any hint how to make this work ? I’ve been struggling with various combinations of parameters, but without success so far… 

Feedback usabilla icon