Home / Blog / PhotosUI picker vs PhotoKit: picking the right one

PhotosUI picker vs PhotoKit: picking the right one

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 = […]

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

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

Have a project on this topic?

Leave a brief summary — I’ll get back to you within 24 hours.

Get in touch