axiom-photo-library-ref

安装量: 114
排名: #7533

安装

npx skills add https://github.com/charleswiltgen/axiom --skill axiom-photo-library-ref

Photo Library API Reference Quick Reference // SWIFTUI PHOTO PICKER (iOS 16+) import PhotosUI

@State private var item: PhotosPickerItem?

PhotosPicker(selection: $item, matching: .images) { Text("Select Photo") } .onChange(of: item) { _, newItem in Task { if let data = try? await newItem?.loadTransferable(type: Data.self) { // Use image data } } }

// UIKIT PHOTO PICKER (iOS 14+) var config = PHPickerConfiguration() config.selectionLimit = 1 config.filter = .images let picker = PHPickerViewController(configuration: config) picker.delegate = self

// SAVE TO CAMERA ROLL try await PHPhotoLibrary.shared().performChanges { PHAssetCreationRequest.creationRequestForAsset(from: image) }

// CHECK PERMISSION let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)

PHPickerViewController (iOS 14+)

System photo picker for UIKit apps. No permission required.

Configuration import PhotosUI

var config = PHPickerConfiguration()

// Selection limit (0 = unlimited) config.selectionLimit = 5

// Filter by asset type config.filter = .images

// Use photo library (enables asset identifiers) config = PHPickerConfiguration(photoLibrary: .shared())

// Preferred asset representation config.preferredAssetRepresentationMode = .automatic // default // .current - original format // .compatible - converted to compatible format

Filter Options // Basic filters PHPickerFilter.images PHPickerFilter.videos PHPickerFilter.livePhotos

// Combined filters PHPickerFilter.any(of: [.images, .videos])

// Exclusion filters (iOS 15+) PHPickerFilter.all(of: [.images, .not(.screenshots)]) PHPickerFilter.not(.livePhotos)

// Playback style filters (iOS 17+) PHPickerFilter.any(of: [.cinematicVideos, .slomoVideos])

Presenting let picker = PHPickerViewController(configuration: config) picker.delegate = self present(picker, animated: true)

Delegate extension ViewController: PHPickerViewControllerDelegate {

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    picker.dismiss(animated: true)

    for result in results {
        // Get asset identifier (if using PHPickerConfiguration(photoLibrary:))
        let identifier = result.assetIdentifier

        // Load as UIImage
        result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
            guard let image = object as? UIImage else { return }
            DispatchQueue.main.async {
                self.displayImage(image)
            }
        }

        // Load as Data
        result.itemProvider.loadDataRepresentation(forTypeIdentifier: UTType.image.identifier) { data, error in
            guard let data else { return }
            // Use data
        }

        // Load Live Photo
        result.itemProvider.loadObject(ofClass: PHLivePhoto.self) { object, error in
            guard let livePhoto = object as? PHLivePhoto else { return }
            // Use live photo
        }
    }
}

}

PHPickerResult Properties Property Type Description itemProvider NSItemProvider Provides selected asset data assetIdentifier String? PHAsset identifier (if using photoLibrary config) PhotosPicker (SwiftUI, iOS 16+)

SwiftUI view for photo selection. No permission required.

Basic Usage import SwiftUI import PhotosUI

// Single selection @State private var selectedItem: PhotosPickerItem?

PhotosPicker(selection: $selectedItem, matching: .images) { Label("Select Photo", systemImage: "photo") }

// Multiple selection @State private var selectedItems: [PhotosPickerItem] = []

PhotosPicker( selection: $selectedItems, maxSelectionCount: 5, matching: .images ) { Text("Select Photos") }

Filters // Images only matching: .images

// Videos only matching: .videos

// Images and videos matching: .any(of: [.images, .videos])

// Live Photos matching: .livePhotos

// Exclude screenshots (iOS 15+) matching: .all(of: [.images, .not(.screenshots)])

Selection Behavior PhotosPicker( selection: $items, maxSelectionCount: 10, selectionBehavior: .ordered, // .default, .ordered, .continuous matching: .images ) { ... }

Behavior Description .default Standard multi-select .ordered Selection order preserved .continuous Live updates as user selects (iOS 17+) Embedded Picker (iOS 17+) PhotosPicker( selection: $items, maxSelectionCount: 10, selectionBehavior: .continuous, matching: .images ) { Text("Select") } .photosPickerStyle(.inline) // Embed in view hierarchy .photosPickerDisabledCapabilities([.selectionActions]) .photosPickerAccessoryVisibility(.hidden, edges: .all)

Style Description .presentation Modal sheet (default) .inline Embedded in view .compact Single row Disabled Capability Effect .search Hide search bar .collectionNavigation Hide albums .stagingArea Hide selection review .selectionActions Hide Add/Cancel Accessory Visibility Description .hidden, .automatic, .visible Per edge HDR Preservation (iOS 17+) PhotosPicker( selection: $items, matching: .images, preferredItemEncoding: .current // Don't transcode, preserve HDR ) { ... }

Encoding Description .automatic System decides format .current Original format, preserves HDR .compatible Force compatible format Loading Images from PhotosPickerItem // Load as Data (most reliable) if let data = try? await item.loadTransferable(type: Data.self), let image = UIImage(data: data) { // Use image }

// Custom Transferable for direct UIImage struct ImageTransferable: Transferable { let image: UIImage

static var transferRepresentation: some TransferRepresentation {
    DataRepresentation(importedContentType: .image) { data in
        guard let image = UIImage(data: data) else {
            throw TransferError.importFailed
        }
        return ImageTransferable(image: image)
    }
}

}

// Usage if let result = try? await item.loadTransferable(type: ImageTransferable.self) { let image = result.image }

PhotosPickerItem Properties Property Type Description itemIdentifier String Unique identifier supportedContentTypes [UTType] Available representations PhotosPickerItem Methods // Load transferable func loadTransferable(type: T.Type) async throws -> T?

// Load with progress func loadTransferable( type: T.Type, completionHandler: @escaping (Result) -> Void ) -> Progress

PHPhotoLibrary

Access and modify the photo library.

Authorization Status // Check current status let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)

// Request authorization let newStatus = await PHPhotoLibrary.requestAuthorization(for: .readWrite)

PHAuthorizationStatus Status Description .notDetermined User hasn't been asked .restricted Parental controls limit access .denied User denied access .authorized Full access granted .limited Access to user-selected photos only (iOS 14+) Access Levels // Read and write PHPhotoLibrary.requestAuthorization(for: .readWrite)

// Add only (save photos, no reading) PHPhotoLibrary.requestAuthorization(for: .addOnly)

Limited Library Picker // Present picker to expand limited selection @MainActor func presentLimitedLibraryPicker() { guard let viewController = UIApplication.shared.keyWindow?.rootViewController else { return } PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController) }

// With completion handler PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController) { identifiers in // identifiers: asset IDs user added }

Performing Changes // Async changes try await PHPhotoLibrary.shared().performChanges { // Create, update, or delete assets }

// With completion handler PHPhotoLibrary.shared().performChanges({ // Changes }) { success, error in // Handle result }

Change Observer class PhotoObserver: NSObject, PHPhotoLibraryChangeObserver {

override init() {
    super.init()
    PHPhotoLibrary.shared().register(self)
}

deinit {
    PHPhotoLibrary.shared().unregisterChangeObserver(self)
}

func photoLibraryDidChange(_ changeInstance: PHChange) {
    // Handle changes
    guard let changes = changeInstance.changeDetails(for: fetchResult) else { return }

    DispatchQueue.main.async {
        // Update UI with new fetch result
        let newResult = changes.fetchResultAfterChanges
    }
}

}

PHAsset

Represents an asset in the photo library.

Fetching Assets // All photos let allPhotos = PHAsset.fetchAssets(with: .image, options: nil)

// With options let options = PHFetchOptions() options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] options.fetchLimit = 100 options.predicate = NSPredicate(format: "mediaType == %d", PHAssetMediaType.image.rawValue)

let recentPhotos = PHAsset.fetchAssets(with: options)

// By identifier let assets = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil)

Asset Properties Property Type Description localIdentifier String Unique ID mediaType PHAssetMediaType .image, .video, .audio mediaSubtypes PHAssetMediaSubtype .photoLive, .photoPanorama, etc. pixelWidth Int Width in pixels pixelHeight Int Height in pixels creationDate Date? When taken modificationDate Date? Last modified location CLLocation? GPS location duration TimeInterval Video duration isFavorite Bool Marked as favorite isHidden Bool In hidden album PHAssetMediaType Type Value .unknown 0 .image 1 .video 2 .audio 3 PHAssetMediaSubtype Subtype Description .photoPanorama Panoramic photo .photoHDR HDR photo .photoScreenshot Screenshot .photoLive Live Photo .photoDepthEffect Portrait mode .videoStreamed Streamed video .videoHighFrameRate Slo-mo video .videoTimelapse Timelapse .videoCinematic Cinematic mode PHAssetCreationRequest

Create new assets in the photo library.

Creating from UIImage try await PHPhotoLibrary.shared().performChanges { PHAssetCreationRequest.creationRequestForAsset(from: image) }

Creating from File URL try await PHPhotoLibrary.shared().performChanges { PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: imageURL) }

// For video try await PHPhotoLibrary.shared().performChanges { PHAssetCreationRequest.creationRequestForAssetFromVideo(atFileURL: videoURL) }

Creating with Resources try await PHPhotoLibrary.shared().performChanges { let request = PHAssetCreationRequest.forAsset()

// Add photo resource
let options = PHAssetResourceCreationOptions()
options.shouldMoveFile = true  // Move instead of copy

request.addResource(with: .photo, fileURL: photoURL, options: options)

// Set creation date
request.creationDate = Date()

// Set location
request.location = CLLocation(latitude: 37.7749, longitude: -122.4194)

}

Deferred Photo Proxy (iOS 17+)

Save camera proxy photos for background processing:

// From AVCaptureDeferredPhotoProxy callback try await PHPhotoLibrary.shared().performChanges { let request = PHAssetCreationRequest.forAsset()

// Use .photoProxy to trigger deferred processing
request.addResource(with: .photoProxy, data: proxyData, options: nil)

}

Resource Type Description .photo Standard photo .video Video file .photoProxy Deferred processing proxy (iOS 17+) .adjustmentData Edit adjustments Getting Created Asset try await PHPhotoLibrary.shared().performChanges { let request = PHAssetCreationRequest.forAsset() request.addResource(with: .photo, fileURL: url, options: nil)

// Get placeholder for later fetching
let placeholder = request.placeholderForCreatedAsset
// placeholder.localIdentifier available after changes complete

}

PHFetchResult

Ordered list of assets from a fetch.

Properties Property Type Description count Int Number of items firstObject T? First item lastObject T? Last item Methods // Access by index let asset = fetchResult.object(at: 0) let asset = fetchResult[0]

// Get multiple let assets = fetchResult.objects(at: IndexSet(0..<10))

// Iteration fetchResult.enumerateObjects { asset, index, stop in // Process asset if shouldStop { stop.pointee = true } }

// Check contains let contains = fetchResult.contains(asset) let index = fetchResult.index(of: asset)

PHImageManager

Request images from assets.

Request Image let manager = PHImageManager.default()

let options = PHImageRequestOptions() options.deliveryMode = .highQualityFormat options.resizeMode = .exact options.isNetworkAccessAllowed = true // For iCloud photos

let targetSize = CGSize(width: 300, height: 300)

manager.requestImage( for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options ) { image, info in guard let image else { return }

// Check if this is the final image
let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool) ?? false
if !isDegraded {
    // Final high-quality image
}

}

PHImageRequestOptions Property Type Description deliveryMode PHImageRequestOptionsDeliveryMode Quality preference resizeMode PHImageRequestOptionsResizeMode Resize behavior isNetworkAccessAllowed Bool Allow iCloud download isSynchronous Bool Synchronous request progressHandler Block Download progress allowSecondaryDegradedImage Bool Extra callback during deferred processing (iOS 17+) Secondary Degraded Image (iOS 17+)

For photos undergoing deferred processing, get an intermediate quality image:

let options = PHImageRequestOptions() options.allowSecondaryDegradedImage = true

// Callback order: // 1. Low quality (immediate, isDegraded = true) // 2. Medium quality (new, isDegraded = true) -- while processing // 3. Final quality (isDegraded = false)

Delivery Modes Mode Description .opportunistic Fast thumbnail, then high quality .highQualityFormat Only high quality .fastFormat Only fast/degraded Request Video manager.requestAVAsset(forVideo: asset, options: nil) { avAsset, audioMix, info in guard let avAsset else { return } // Use AVAsset for playback }

// Or export to file manager.requestExportSession( forVideo: asset, options: nil, exportPreset: AVAssetExportPresetHighestQuality ) { session, info in session?.outputURL = outputURL session?.outputFileType = .mp4 session?.exportAsynchronously { ... } }

PHChange

Represents changes to the photo library.

Getting Change Details func photoLibraryDidChange(_ changeInstance: PHChange) { guard let changes = changeInstance.changeDetails(for: fetchResult) else { return }

// Check what changed
let hasIncrementalChanges = changes.hasIncrementalChanges
let insertedIndexes = changes.insertedIndexes
let removedIndexes = changes.removedIndexes
let changedIndexes = changes.changedIndexes

// Get new fetch result
let newResult = changes.fetchResultAfterChanges

// Update collection view
DispatchQueue.main.async {
    if hasIncrementalChanges {
        collectionView.performBatchUpdates {
            if let removed = removedIndexes {
                collectionView.deleteItems(at: removed.map { IndexPath(item: $0, section: 0) })
            }
            if let inserted = insertedIndexes {
                collectionView.insertItems(at: inserted.map { IndexPath(item: $0, section: 0) })
            }
            if let changed = changedIndexes {
                collectionView.reloadItems(at: changed.map { IndexPath(item: $0, section: 0) })
            }
        }
    } else {
        collectionView.reloadData()
    }
}

}

Common Code Patterns Complete Photo Gallery View import SwiftUI import Photos

@MainActor class PhotoGalleryViewModel: ObservableObject { @Published var assets: [PHAsset] = [] @Published var authorizationStatus: PHAuthorizationStatus = .notDetermined

func requestAccess() async {
    authorizationStatus = await PHPhotoLibrary.requestAuthorization(for: .readWrite)

    if authorizationStatus == .authorized || authorizationStatus == .limited {
        fetchAssets()
    }
}

func fetchAssets() {
    let options = PHFetchOptions()
    options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
    options.fetchLimit = 100

    let result = PHAsset.fetchAssets(with: .image, options: options)
    assets = result.objects(at: IndexSet(0..<result.count))
}

func expandLimitedAccess(from viewController: UIViewController) {
    PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController)
}

}

struct PhotoGalleryView: View { @StateObject private var viewModel = PhotoGalleryViewModel()

var body: some View {
    Group {
        switch viewModel.authorizationStatus {
        case .authorized, .limited:
            PhotoGridView(assets: viewModel.assets)
        case .denied, .restricted:
            PermissionDeniedView()
        case .notDetermined:
            RequestAccessView {
                Task { await viewModel.requestAccess() }
            }
        @unknown default:
            EmptyView()
        }
    }
    .task {
        await viewModel.requestAccess()
    }
}

}

Resources

Docs: /photosui/phpickerviewcontroller, /photosui/photospicker, /photos/phphotolibrary, /photos/phasset, /photos/phimagemanager

Skills: axiom-photo-library, axiom-camera-capture

返回排行榜