Introduced
Every day, the number of photos taken with the iPhone exceeds any camera. Every year, the display on IOS devices is getting better, and back in the days when the IPad just appeared without the Retina display, one of the killer features of the big screen was the ability to showcase user photos and browser photo galleries. Since the camera became the most important and popular feature of the IPhone, there is a huge need for applications and tools that can manage and process the valuable photos in the user photo gallery.
Until the summer of 2014, developers could only use the Assetslibrary framework to access the growing user's photo gallery. For several years, camera applications and photo applications have changed dramatically, adding many new features, including how to organize photos at a moment's time. But at the same time, the assetslibrary framework did not keep pace.
With the advent of IOS 8, Apple has provided us with a modern framework--photokit, which is better than Assetslibrary and has the features to make apps and device photo libraries work seamlessly.
Profile
We'll start with a bird's eye view of the framework object model: Relationships between entities and entities, get instances of entities, and work with the results obtained.
In addition to this, our presentation will involve some resource metadata that has not yet been opened to developers when using Assetslibrary.
We then discuss the image data that loads the resource: the process itself, a number of available options, some traps and boundary cases.
Finally, we'll talk about the changes in the photo gallery through external participants and learn how to create and submit changes to our own changes.
Photokit Object Model
Photokit defines an entity chart that is consistent with the model objects presented to the user within the Photos application of the system. These photo entities are lightweight, immutable objects. All Photokit objects are inherited from the PHObject
abstract base class, and their public interface provides only one localIdentifier
property.
PHAsset
Represents a separate resource in the user photo Gallery that provides metadata for the resource.
A group of resources is called a resource collection, represented by a PHAssetCollection
class. A separate collection of resources can be an album in photo gallery or a moment, or a special "smart album". This smart album includes all the video collections, recently added items, user favorites, all burst photos, and more. PHAssetCollection
is PHCollection
the child class.
PHCollectionList
Represents a group of PHCollections
. Because it is itself PHCollection
, the collection list can contain a list of other collections that allow complex collections to inherit. In fact, we can see it in the time column of the photo app: Photo---Moment---selected---year, is an example.
Get (FETCH) photo entity get vs. enumeration
Developers who are familiar with the assetslibrary framework may remember that assetslibrary can use some specific attributes to find the resources they need, one of which must enumerate the user repository to get the matching resources. Admittedly, this API provides some way to narrow down the search domain, but it's still very inefficient.
In stark contrast, examples of photokit entities are obtained by acquiring them. Those who are familiar with Core Data will feel that they are closer to photokit in terms of concepts and descriptions.
GET request
The get operation is implemented by the class method of the entity described above. Which class/method to use depends on the scope of the problem and how you show and traverse the photo gallery. The naming of all get methods is similar: class func fetchXXX(..., options: PHFetchOptions) -> PHFetchResult
. The options
parameters give us a way to filter and sort the results, which NSFetchRequest
is similar to predicate
the sortDescriptors
parameters.
Get results
You may have noticed that these fetch operations are not asynchronous. They return an PHFetchResult
object that can use a similar NSArray
interface to access the collection within the result. It dynamically loads content on demand and caches the most recently requested content. This behavior batchSize
is similar to the NSFetchRequest
returned result array that sets the property. PHFetchResult
There is no way to specify this behavior with parameters, but the official website document guarantees "the best performance even when dealing with a large number of return results".
The object returned by the Get method PHFetchResult
is not automatically updated even if the content of the photo library that satisfies the request has changed. In a later section, we'll show you how to look at the changes to the returned PHFetchResult
object and work with the updated content.
Temporary collection (Transient collections)
You may find that you have designed a component that can manipulate the resource collection, and you want it to be able to handle any set of resources. Photokit through a collection of temporary resources, so that we can easily do this.
You can PHAsset
create a temporary collection of resources through an object array or an object that contains resources PHFetchResult
. The actions created are PHAssetCollection
transientAssetCollectionWithAssets(...)
transientAssetCollectionWithFetchResult(...)
completed within the and factory methods. The objects created by these methods can be PHAssetCollection
used just like other objects. However, these collections are not stored in the user photo gallery and are naturally not displayed in the photo app.
Similar to the resource collection, you can use PHCollectionList
the transientCollectionListWithXXX(...)
factory method in to create a temporary collection list.
When you want to merge two request requests, you will find that this thing is very useful.
Photo Meta data
As mentioned at the beginning of the article, Photokit provides additional metadata about the user's resources, which are not accessible in the previously used alassetslibrary framework or are difficult to access.
HDR and Panorama photos
You can use the properties of the photo resource to verify that the mediaSubtypes
image in the asset Library has HDR turned on when capturing, and whether the camera app's Panorama mode was used when shooting.
Collect and hide Resources
To verify that a resource is marked as favorite or hidden by the user, simply check PHAsset
the instance's favorite
and hidden
properties.
Burst Mode photos
For a resource, if its PHAsset
representsBurst
property is, it means that true
the resource is a representative photo in a series of burst photos (multiple photos were taken while the user was holding down the shutter). It also has a property burstIdentifier
that if you want to get the remaining other photos in a burst photo, you can get it by passing this value into the fetchAssetsWithBurstIdentifier(...)
method.
The user can mark a shot in a continuous sequence, and the system automatically uses various heuristics to mark a potential representative photo that the user may choose. This metadata is PHAsset
accessible through the burstSelectionTypes
properties. This property is a bitmask of three constants: a .UserPick
resource that represents a manually tagged user, represents a .AutoPick
potential resource that a user might tag, and .None
represents a resource that is not marked.
This screenshot shows how the photo app automatically marks potential resources that a user might tag in a burst photo.
Photo Loading
In the past few years of working with user photo galleries, developers have created hundreds (if not thousands) of tips to improve the efficiency of photo loading and presentation. These techniques handle the dispatch and cancellation of requests, image size modification and cropping, caching, and so on. Photokit provides a class that can do all of these things with a more convenient and modern API: PHImageManager
.
Request image
Image requests are requestImageForAsset(...)
distributed through methods. This method accepts one PHAsset
that can set the size of the returned image and other optional options for the image (via the PHImageRequestOptions
Parameter object settings), and the result callback (results handler). The return value of this method can be used to cancel the request when the requested data is no longer required.
Size and cropping of images
Oddly, the parameters for the size definition and clipping of the returned image are distributed in two places. targetSize
and contentMode
These parameters are passed directly inside the requestImageForAsset(...)
method. This content mode is similar to the UIView contentMode
parameter, which determines whether the photo should be placed in the target size in a scaled or proportional manner. Note: If you do not modify or crop the photo size, then the method parameter is the PHImageManagerMaximumSize
and PHImageContentMode.Default
.
In addition, PHImageRequestOptions
there are several ways to determine how the image Manager will resize the image. resizeMode
property can be set to .Exact
(The return image must match the target size), .Fast
(ratio.) Exact is more efficient, but the returned image may not be the same as the target size) or .None
. Also worth mentioning is that the normalizedCroppingMode
attribute lets us determine how the image manager should crop the image. Note: If you set normalizedcroppingMode
the value, you resizeMode
need to set it to .Exact
.
Request delivery and Progress
By default, if the image manager decides to use an optimal policy, it passes a lower-quality version before delivering a high-quality version of the image to you. You can deliveryMode
control this behavior through attributes, and the value of the default behavior described above is .Opportunistic
. If you only want high-quality images and can accept longer load times, set the property to .HighQualityFormat
. If you want to load faster, and you can sacrifice a bit of image quality, set the property to .FastFormat
.
You can use PHImageRequestOptions
the synchronous
properties to make the requestImage...
series method into a synchronous operation. Note: When synchronous
set to true
, the deliveryMode
property is ignored and treated as a .HighQualityFormat
.
When setting these parameters, it is important to consider that some of your users might have ICloud photo gallery turned on. The Photokit API doesn't necessarily differentiate between photos on your device and photos on ICloud-they all load in the same requestImage
way. This means that any image request is likely to be a very slow network request over a cellular network. Keep this in mind when you want to use .HighQualityFormat
or make a synchronous request. Note: If you want to make sure that the request does not go through the network, it can be networkAccessAllowed
set to false
.
Another ICloud-related property is progressHandler
. You can set it to a phassetimageprogresshandler block, which is automatically called by the image Manager when you download photos from ICloud.
Resource version
Photokit allows the app to make lossless changes to the photo. For edited photos, a copy of the original photo and adjustment data for the app are saved separately. When you use the Image Manager to get a resource, you can specify which version of the image resource should be delivered through result handler. This can be done by setting version
properties that .Current
will deliver images that contain all adjustments and modifications, .Unadjusted
deliver images that have not been modified, .Original
and deliver images in the original, highest-quality format, such as raw data. When the property is set to .Unadjusted
, a JPEG is delivered.
You can read more about this in the framework in Sam Davies's article, "Photo extensions".
Results callback (Result handler)
The result callback is a UIImage
block that contains a variable and a info
dictionary as a parameter. Depending on the parameters and the requested option, it can be called multiple times by the image manager throughout the lifetime of the request.
info
The dictionary provides information about the status of the current request, such as:
- Whether the image must be requested from ICloud (if you set it up when you initialize it
networkAccessAllowed
false
, you must re-request the image)-- PHImageResultIsInCloudKey
.
- The low-
UIImage
quality format of whether the current delivery is the final result. When high-quality images are being downloaded, this allows you to show a preview image to the user first- PHImageResultIsDegradedKey
.
- The request ID (which makes it easy to cancel the request), and whether the request has been canceled--
PHImageResultRequestIDKey
and PHImageCancelledKey
.
- If no image is provided to result handler, there is an error message in the dictionary--
PHImageErrorKey
.
These values allow you to update your UI to inform the user, as discussed above progressHandler
, to indicate their loading status.
Cache
When images are about to appear on the screen, such as when you want to display a large number of thumbnail images of a resource image on a set of scrolling collection views, it is sometimes useful to preload some images into memory. Photokit provides a PHImageManager
subclass to handle This particular usage scenario-- PHImageCachingManager
.
PHImageCachingManager
Provides a key approach- startCachingImagesForAssets(...)
. You pass in an PHAssets
array of types, some request parameters, and some optional options that you will use to request a single image. In addition, there are methods that let you notify the cache manager to stop caching a specific list of resources and to stop caching all images.
allowsCachingHighQualityImages
property allows you to specify whether the image manager should prepare high-quality images. The default properties behave well when caching a short and immutable list of resources true
. However, when you want to do a cache operation on the collection view, it is best to set it to false
.
Note: In my experience, using the cache manager can damage the performance of a slide when users are sliding extremely fast on a collection view with a large amount of resources. It is extremely important to customize a caching behavior for this particular usage scenario. The size of the cache window, the time and frequency of moving the cache window, allowsCachingHighQualityImages
the attribute values--these parameters are carefully adjusted in the real photo library on the target hardware and tested for performance. Further, you can consider dynamically setting these parameters on the basis of user behavior.
Request image Data
Finally, in addition to asking for normal UIImage
, PHImageManager
another method can return NSData
resource data for an object type, including its generic type identifier, and the orientation of the image. This method returns the most information for this resource.
The vicissitudes of Things
We have discussed the metadata for requesting resources in the user photo gallery, but so far we have not mentioned how to update the data we get. Photo Gallery is essentially a bunch of mutable states, and the photo entities mentioned in the first section are immutable objects. Photokit allows you to receive all the information you need about the changes in the photo gallery to properly update your cache status.
Observing changes
First, you need to PHPhotoLibrary
registerChangeObserver(...)
register a change observer by means of a shared object (the observer will obey the PHPhotoLibraryChangeObserver
protocol). As long as another app or user's changes in the photo gallery affect any resource or resource collection that you get before the change, the Change observer's photoLibraryDidChange(...)
method is called. This method has only one PHChange
type of argument that you can use to verify that the changes are related to the object you are interested in acquiring.
Update the results obtained
PHChange
There are several ways to track changes by passing in any object or object that you are interested in PHObject
PHFetchResult
. These several methods are changeDetailsForObject(...)
and changeDetailsForFetchResult(...)
. If there are no changes, these methods will return nil
, otherwise you can use PHObjectChangeDetails
or PHFetchResultChangeDetails
object to observe the changes.
PHObjectChangeDetails
Provides a reference to the most recent photo entity object and a Boolean value that tells you if the object's image data has changed and whether the object has been deleted.
PHFetchResultChangeDetails
Encapsulates the information that is applied before you by getting the PHFetchResult
changes you get. PHFetchResultChangeDetails
is designed to simplify the update operation of CollectionView or TableView as much as possible. Its properties map exactly to the information you need when using a typical CollectionView update handler. Note that to correct the update UITableView
/ UICollectionView
, you must handle the change in the correct order, that is: rice--removedindexes,insertedindexes,changedindexes, Enumeratemoveswithblock (if that's hasMoves
the true
case). In addition, PHFetchResultChangeDetails
the hasIncrementalChanges
properties can be set false
, which means that the old fetch results should all be replaced by the new values. In this case, you should call UITableView/UICollectionView
the reloadData
.
Note: There is no need to handle changes in a centralized manner. If you have multiple components in your app that need to work with photo entities, then each of them has its own PHPhotoLibraryChangeObserver
. The component then can query the object on its own PHChange
and detect if (and how) to update its own state.
Change with the Wind
Now that we know how to observe the changes caused by users and other applications, let's try to make changes ourselves.
Change the objects that exist
Using Photokit to make a change in a photo gallery is to create a change request object that is linked to a resource or resource set, set the properties of the requested object, or call the appropriate method to describe the change you want to commit. This must be performChanges(...)
done through the method, within the block submitted to the share PHPhotoLibrary
. Note: You need to be prepared to performChanges
handle the failure in the completion block of the method. While dealing with a state that can be changed by multiple participants (such as your app, users, other apps, photo extensions, etc.), this approach provides security and is relatively easy to use.
To modify a resource, you need to create one PHAssetChangeRequest
. You can then modify the creation date, the resource location, and whether the resource will be hidden, whether the resource is viewed as a user collection, and so on. In addition, you can delete resources from the user's library.
Similarly, to modify a resource collection or collection list, you need to create one PHAssetCollectionChangeRequest
or an PHCollectionListChangeRequest
object. You can then modify the collection title, add or Remove collection members, or completely delete the collection.
Before your changes are submitted to the user photo gallery, the user is presented with an explicit warning box to get permission.
Create a new object
The practice of creating a new resource is similar to modifying a resource that already exists. Simply use the creationRequestForAssetFromXXX(...)
factory method to create a change request and pass in the resource image data (or a URL). If you need to make additional changes to the newly created resource, you can create the properties of the change request placeholderForCreatedAsset
. It returns an available placeholder instead of a "real" PHAsset
reference.
Conclusion
I've discussed the basics of photokit, but there's still a lot of stuff waiting for us to explore. You can watch the WWDC session video to learn more, dig deeper, and write some of your own code by looking at the code of the sample everywhere: Photokit opens the possibility for IOS developers to new worlds, and in the coming months or years we will certainly see more creative and excellent products based on this foundation.
iOS photo Frame