When iOS 14 brought PhotosUI, I thought I was done with PhotoKit. Three projects in, the tradeoffs are clear: both have a place. Here’s the decision matrix I use.
PhotosUI: simple picking
The user picks one or a few photos or videos, you don’t need extra metadata:
import PhotosUI
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = 5
config.preferredAssetRepresentationMode = .current
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
present(picker, animated: true)In the delegate:
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
for result in results {
result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { url, _ in
// copy the file at url
}
}
}The main advantage: no permission prompt. Apple handles the picker dialog on behalf of the user. The user is not forced to grant “all photos” access. It’s a privacy-first API.
PhotoKit: deeper needs
You need PhotoKit if any of these apply:
- Showing the user’s full library (your own custom picker UI)
- Reading EXIF or other metadata
- Album management (create an album, add or remove assets)
- Processing Live Photo components separately
- Observing changes to the photo library
- Fetching full-quality assets from iCloud
Authorization flow
import Photos
func requestAuth() async -> PHAuthorizationStatus {
await withCheckedContinuation { cont in
PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
cont.resume(returning: status)
}
}
}iOS 14+ introduces a “Selected Photos” state. The user can share only the selected photos without opening the whole library. Your app only sees the chosen set, so your UI has to adapt.
Asset request
let fetch = PHAsset.fetchAssets(with: .image, options: nil)
fetch.enumerateObjects { asset, _, _ in
let options = PHImageRequestOptions()
options.isSynchronous = false
options.deliveryMode = .highQualityFormat
options.isNetworkAccessAllowed = true // fetch from iCloud
PHImageManager.default().requestImage(
for: asset,
targetSize: CGSize(width: 400, height: 400),
contentMode: .aspectFill,
options: options
) { image, info in
// use the image
}
}isNetworkAccessAllowed = true matters: photos stored in iCloud may not be cached locally. If the user wants high resolution, you often need a network fetch.
PhotosUI’s gap: no EXIF
You cannot reach EXIF data through a PHPickerResult. If you need location, capture date, camera model, or similar metadata, you have to go to PhotoKit. Once the user grants permission, you read it off PHAsset:
asset.location // CLLocation
asset.creationDate // Date
asset.pixelWidth // Int
asset.mediaSubtypes // .photoScreenshot, .photoLive, etc.Change observation
The user deletes a photo, adds a new one, edits an existing one. Your app wants to react:
class LibraryObserver: NSObject, PHPhotoLibraryChangeObserver {
func photoLibraryDidChange(_ changeInstance: PHChange) {
// process collection and asset changes
}
}
PHPhotoLibrary.shared().register(observer)PhotosUI doesn’t offer an API for this. Each selection reopens the picker. If you want a gallery-app experience, it’s PhotoKit.
Save to library
Saving an exported photo or video back to the user’s library:
try await PHPhotoLibrary.shared().performChanges {
let request = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: fileURL)
request?.creationDate = Date()
}PhotosUI doesn’t do this. Save-to-library always means PhotoKit.
My decision matrix
One-shot picking, no metadata, no export: PHPickerViewController. Photo editor, photo library explorer, social media feed: PhotoKit. Middle ground: pick with PhotosUI and then fetch the selected asset via PhotoKit using its identifier (you’ll only have access to the selected asset):
// Get the asset identifier from PhotosUI
config.preferredAssetRepresentationMode = .automatic
// In the picker delegate:
let assetId = result.assetIdentifier
let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId!], options: nil).firstObjectThat combination is a good middle ground. No permission prompt, but you still get the full PhotoKit API for the selected photo.
Closing advice
On a new project, default to PhotosUI. Switch to PhotoKit only once the requirements clearly outgrow it. Writing an abstraction that can use both from day one is not smart: if you end up migrating from PhotosUI to PhotoKit, it’s two weeks of work, but PhotosUI is enough end-to-end on 70% of projects.