Streaming is available in most browsers,
and in the Developer app.
-
Discover PhotoKit change history
PhotoKit can help you build rich, photo-centric features. Learn how you can easily track changes to image assets with the latest APIs in PhotoKit. We'll introduce you to the PHPhotoLibrary change history API and demonstrate how you can persist change tokens across launches to help your app recognize additions, deletions, and updates to someone's photo library. To learn more about Photos library integration, be sure to watch "What's new in the Photos picker" from WWDC22 and "Improve access to Photos in your app" from WWDC21.
Resources
Related Videos
WWDC23
WWDC22
WWDC21
-
Download
♪ Mellow instrumental hip-hop music ♪ ♪ Hi, my name is Mindy and I'm an engineer on the Photos team.
Today, I'll be going over how to access Photos change history in your apps.
PhotoKit provides a rich set of APIs for accessing and updating photos, videos, and albums stored in the photo library.
PhotoKit is designed for apps that require a deep level of Photos access and integration for managing or editing photos, custom cameras, or apps that give people a way to browse their photo library in a unique way.
These types of applications may want to monitor how the photo library changes over time to closely mirror the Photos experience.
Let's say I've created a social hiking app that allows people to share and edit photos of hiking trips with friends.
When someone launches the app, the app gathers photos from the start and end timestamp of their latest hiking workout to generate a collage of their experience on the mountain.
The collages stay in sync with the selected photos from the photo library.
If someone receives hiking photos from a friend, for instance, the app will generate new collages using these updates.
Up until now, in order for the app to discover newly inserted assets and changes to previous hiking collages, the app would need to perform a series of fetches.
To determine which assets were inserted, the app can fetch assets with a date created later than the last app launch date.
Determining asset updates and deletions is trickier.
The app would need to refetch every asset in every collage and check the modification date to determine asset updates, but this could bring up false positives, as the asset modification date can be set by internal Photos processing activities.
Deletions in the photo library are more difficult to track, as all tracked assets need to be fetched and diffed for assets that were not returned with the fetch.
In total, this means that there are three separate checks that need to be done each time the app is launched, which can be especially costly if the app is displaying large quantities of assets.
Instead of performing different fetches and checks for uncertain results, what if there was a way to know exactly what changed in one unified API call? Well, I'm excited to say that we've done just that! The new change history API allows for an easier way to track offline updates to the photo library.
Change history consists of a timeline of changes such as insertions, updates, and deletions to the photo library.
In this example timeline, there are a variety of asset, album, and folder changes in the change history from the past three days.
Using this timeline, how can you determine which changes have happened in the last two days, or the last time you launched your app? You can now use a persistent change token that represents the state of the photo library at a given point in time.
This token can be persisted across app launches, and it can be used to fetch changes to the photo library that have occurred since that token, including third-party app changes.
Note that if your app is in limited library mode, only changes for user-selected PhotoKit objects will be returned.
This change token is local to the device and is cheap to access from a persistent change or photo library instance at any time.
This new API is available on every platform that supports PhotoKit: macOS, iOS, iPadOS, and tvOS.
As your app is running and working with the photo library, you can store a persistent change token within the app.
Later, you can use the token to fetch the photo library changes that have occurred since.
For each persistent change, you can grab the change details for three types of Photos objects: asset, asset collection, and collection list.
So what does this look like in code? You first use the last stored change token to fetch the persistent changes.
Next, you enumerate through the persistent changes and grab the change details -- in this case, of type "asset" -- for each persistent change object.
These change details provide information on which local identifiers were updated, deleted, and inserted into the photo library since the change token.
After processing these changes, you can store the last change token for future use.
Let's compare and contrast the new persistent history API with the existing change observer APIs.
PHChanges deal with active, in-memory fetch results and are used to record live changes to the photo library while your app is running.
Persistent history, on the other hand, records long-running changes to the photo library, and can be used to report changes from when your app is not active.
You can use both or either of these APIs depending on the requirements of your app.
Going back to the hiking app example, I'd like to now use the persistent history API to track asset changes in order to create and update hiking collages.
First, I'll use the last stored change token and fetch the persistent changes.
Next, I'll iterate through the persistent changes, grab the relevant asset change details, and process the inserted, updated, and deleted identifiers.
Now I need to identify library changes that affect the app from the change history, as the app doesn't need all of the information returned from fetching changes.
It's important for the app to know which assets were added to the photo library for new hiking workouts, and updated and deleted assets referenced in previous hiking collages.
I've already identified the three sets of inserted, updated, and deleted asset local identifiers from enumerating through the persistent changes.
How do I now update the app to reflect this? Using the insertedIdentifiers set, I can determine which assets were added between hiking timestamps by fetching the inserted assets and checking their creation dates against each hike start and end date.
The updated assets may now have adjustments applied, so I can use the new hasAdjustments API to check if I need to redraw the asset in the UI.
I can use the deleted asset local identifiers to determine which collages need to be regenerated.
Now I've handled all of the offline photo library changes and my app is up to date.
Here are a few things you should keep in mind when using the new change history API.
First, determine what changes are important to you and your app, and only check for those changes.
Consider performing one large fetch request of updated and inserted assets instead of multiple smaller requests to improve performance.
Photo libraries can change a lot due to processing and sync activity under the hood, so you may end up enumerating through a large amount of changes, especially if your app is not frequently launched.
Because of this, we recommend that you ask for change history on a background thread to not block the UI.
There are two types of errors that can occur when fetching persistent history.
If the change token is older than the available history of changes, the expired change token error will be returned.
In some cases, the persistent change cannot be relied upon to completely reconstruct the changes that occurred, and will return an error that change details are unavailable.
In these instances, we recommend refetching tracked objects in the photo library to ensure your app is up to date.
Before we wrap up, there are a few more new PhotoKit APIs I'd like to share with you.
PhotoKit now supports accessing cinematic videos by media subtype and smart album.
There are also two new error codes.
If the photo library bundle is in a File Provider sync root directory on macOS, the library can become corrupted, and an error will be returned when trying to perform changes.
If an asset resource cannot be found due to network issues, the resource request will now return a network error.
Please check the developer documentation for all of the latest updates.
Lastly, be sure to check out this year's session on the Photos picker, as it is the easiest way to work with and access Photos.
We're so excited for you to use the new change history API and all of the great new features in PhotoKit.
Thank you! ♪
-
-
0:01 - Tracking photo library changes
// Discover added assets let options = PHFetchOptions() options.predicate = NSPredicate(format: "creationDate > %@", lastLaunchDate as CVarArg) let insertedAssets = PHAsset.fetchAssets(with: options)
-
0:02 - Tracking photo library changes (2)
let fetchResult = PHAsset.fetchAssets(with: localIdentifiers, options: nil) // Discover all modified and deleted assets fetchResult.enumerateObjects { asset, idx, stop in if asset.modificationDate?.compare(lastLaunchDate) == .orderedDescending { // Asset could have been modified } if !localIdentifiers.contains(asset.localIdentifier) { // Asset could have been deleted } }
-
0:03 - Fetching persistent changes using persistent change token
let persistentChanges = try! PHPhotoLibrary.shared().fetchPersistentChanges(since: self.lastStoredToken) for persistentChange in persistentChanges { if let changeDetails = persistentChange.changeDetails(for: PHObjectType.asset) { let updatedIdentifiers = changeDetails.updatedLocalIdentifiers let deletedIdentifiers = changeDetails.deletedLocalIdentifiers let insertedIdentifiers = changeDetails.insertedLocalIdentifiers } } // After processing change details self.lastStoredToken = lastPersistentChange.changeToken
-
0:04 - Identifying important changes
// Get last stored change token let changeToken = self.lastStoredToken // Fetch persistent changes let persistentChanges = try! library.fetchPersistentChanges(since: changeToken) for persistentChange in persistentChanges { // Grab change details and process updates }
-
0:05 - Using inserted identifiers
let insertedAssets = PHAsset.fetchAssets(with: insertedIdentifiers, options: nil) insertedAssets.enumerateObjects { asset, idx, stop in for hike in hikes { let dateInterval = NSDateInterval(start: hike.startDate, end: hike.endDate) if dateInterval.contains(asset.creationDate) { // This hike contains a new added asset } } }
-
0:06 - Using updated identifiers
let updatedAssets = PHAsset.fetchAssets(with: updatedIdentifiers, options: nil) updatedAssets.enumerateObjects { asset, idx, stop in if asset.hasAdjustments { // This asset has edits } }
-
0:07 - Using deleted identifiers
for deletedIdentifier in deletedIdentifiers { for collage in collages { if collage.assetLocalIdentifiers.contains(deletedIdentifier) { // This collage needs to be redrawn } } }
-
0:08 - Handling errors
do { let persistentChanges = try library.fetchPersistentChanges(since: changeToken) } catch PHPhotosError.persistentChangeTokenExpired, PHPhotosError.persistentChangeDetailsUnavailable { let fetchResult = PHAsset.fetchAssets(with: trackedIdentifiers, options: options) // Use fetch result }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.