axiom-photo-library-ref

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Photo Library API Reference

照片库API参考文档

Quick Reference

快速参考

swift
// 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)

swift
// 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+)

PHPickerViewController (iOS 14+)

System photo picker for UIKit apps. No permission required.
适用于UIKit应用的系统照片选择器,无需权限。

Configuration

配置

swift
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
swift
import PhotosUI

var config = PHPickerConfiguration()

// 选择数量限制(0表示无限制)
config.selectionLimit = 5

// 按资源类型筛选
config.filter = .images

// 使用照片库(启用资源标识符)
config = PHPickerConfiguration(photoLibrary: .shared())

// 首选资源表示模式
config.preferredAssetRepresentationMode = .automatic  // 默认值
// .current - 原始格式
// .compatible - 转换为兼容格式

Filter Options

筛选选项

swift
// 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])
swift
// 基础筛选器
PHPickerFilter.images
PHPickerFilter.videos
PHPickerFilter.livePhotos

// 组合筛选器
PHPickerFilter.any(of: [.images, .videos])

// 排除筛选器(iOS 15+)
PHPickerFilter.all(of: [.images, .not(.screenshots)])
PHPickerFilter.not(.livePhotos)

// 播放风格筛选器(iOS 17+)
PHPickerFilter.any(of: [.cinematicVideos, .slomoVideos])

Presenting

展示选择器

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

Delegate

代理方法

swift
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
            }
        }
    }
}
swift
extension ViewController: PHPickerViewControllerDelegate {

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

        for result in results {
            // 获取资源标识符(仅当使用PHPickerConfiguration(photoLibrary:)时可用)
            let identifier = result.assetIdentifier

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

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

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

PHPickerResult Properties

PHPickerResult 属性

PropertyTypeDescription
itemProvider
NSItemProviderProvides selected asset data
assetIdentifier
String?PHAsset identifier (if using photoLibrary config)

属性类型描述
itemProvider
NSItemProvider提供选中资源的数据
assetIdentifier
String?PHAsset标识符(仅当使用photoLibrary配置时可用)

PhotosPicker (SwiftUI, iOS 16+)

PhotosPicker (SwiftUI, iOS 16+)

SwiftUI view for photo selection. No permission required.
用于照片选择的SwiftUI视图,无需权限。

Basic Usage

基础用法

swift
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")
}
swift
import SwiftUI
import PhotosUI

// 单选
@State private var selectedItem: PhotosPickerItem?

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

// 多选
@State private var selectedItems: [PhotosPickerItem] = []

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

Filters

筛选器

swift
// 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)])
swift
// 仅图片
matching: .images

// 仅视频
matching: .videos

// 图片和视频
matching: .any(of: [.images, .videos])

// Live Photos
matching: .livePhotos

// 排除截图(iOS 15+)
matching: .all(of: [.images, .not(.screenshots)])

Selection Behavior

选择行为

swift
PhotosPicker(
    selection: $items,
    maxSelectionCount: 10,
    selectionBehavior: .ordered,  // .default, .ordered, .continuous
    matching: .images
) { ... }
BehaviorDescription
.default
Standard multi-select
.ordered
Selection order preserved
.continuous
Live updates as user selects (iOS 17+)
swift
PhotosPicker(
    selection: $items,
    maxSelectionCount: 10,
    selectionBehavior: .ordered,  // .default, .ordered, .continuous
    matching: .images
) { ... }
行为描述
.default
标准多选模式
.ordered
保留选择顺序
.continuous
用户选择时实时更新(iOS 17+)

Embedded Picker (iOS 17+)

嵌入式选择器(iOS 17+)

swift
PhotosPicker(
    selection: $items,
    maxSelectionCount: 10,
    selectionBehavior: .continuous,
    matching: .images
) {
    Text("Select")
}
.photosPickerStyle(.inline)  // Embed in view hierarchy
.photosPickerDisabledCapabilities([.selectionActions])
.photosPickerAccessoryVisibility(.hidden, edges: .all)
StyleDescription
.presentation
Modal sheet (default)
.inline
Embedded in view
.compact
Single row
Disabled CapabilityEffect
.search
Hide search bar
.collectionNavigation
Hide albums
.stagingArea
Hide selection review
.selectionActions
Hide Add/Cancel
Accessory VisibilityDescription
.hidden
,
.automatic
,
.visible
Per edge
swift
PhotosPicker(
    selection: $items,
    maxSelectionCount: 10,
    selectionBehavior: .continuous,
    matching: .images
) {
    Text("Select")
}
.photosPickerStyle(.inline)  // 嵌入视图层级
.photosPickerDisabledCapabilities([.selectionActions])
.photosPickerAccessoryVisibility(.hidden, edges: .all)
样式描述
.presentation
模态弹窗(默认)
.inline
嵌入视图中
.compact
单行显示
禁用功能效果
.search
隐藏搜索栏
.collectionNavigation
隐藏相册列表
.stagingArea
隐藏选择预览
.selectionActions
隐藏添加/取消按钮
附件可见性描述
.hidden
,
.automatic
,
.visible
按边缘设置

HDR Preservation (iOS 17+)

HDR 保留(iOS 17+)

swift
PhotosPicker(
    selection: $items,
    matching: .images,
    preferredItemEncoding: .current  // Don't transcode, preserve HDR
) { ... }
EncodingDescription
.automatic
System decides format
.current
Original format, preserves HDR
.compatible
Force compatible format
swift
PhotosPicker(
    selection: $items,
    matching: .images,
    preferredItemEncoding: .current  // 不转码,保留HDR
) { ... }
编码方式描述
.automatic
系统自动决定格式
.current
原始格式,保留HDR
.compatible
强制转换为兼容格式

Loading Images from PhotosPickerItem

从PhotosPickerItem加载图片

swift
// 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
}
swift
// 加载为Data(最可靠)
if let data = try? await item.loadTransferable(type: Data.self),
   let image = UIImage(data: data) {
    // Use image
}

// 自定义Transferable直接获取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)
        }
    }
}

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

PhotosPickerItem Properties

PhotosPickerItem 属性

PropertyTypeDescription
itemIdentifier
StringUnique identifier
supportedContentTypes
[UTType]Available representations
属性类型描述
itemIdentifier
String唯一标识符
supportedContentTypes
[UTType]可用的资源表示类型

PhotosPickerItem Methods

PhotosPickerItem 方法

swift
// Load transferable
func loadTransferable<T: Transferable>(type: T.Type) async throws -> T?

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

swift
// 加载可传输对象
func loadTransferable<T: Transferable>(type: T.Type) async throws -> T?

// 带进度的加载
func loadTransferable<T: Transferable>(
    type: T.Type,
    completionHandler: @escaping (Result<T?, Error>) -> Void
) -> Progress

PHPhotoLibrary

PHPhotoLibrary

Access and modify the photo library.
访问和修改照片库。

Authorization Status

授权状态

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

// Request authorization
let newStatus = await PHPhotoLibrary.requestAuthorization(for: .readWrite)
swift
// 检查当前状态
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)

// 请求授权
let newStatus = await PHPhotoLibrary.requestAuthorization(for: .readWrite)

PHAuthorizationStatus

PHAuthorizationStatus

StatusDescription
.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+)
状态描述
.notDetermined
用户尚未被询问授权
.restricted
家长控制限制了访问权限
.denied
用户拒绝了访问权限
.authorized
已授予完全访问权限
.limited
仅能访问用户选择的照片(iOS 14+)

Access Levels

访问级别

swift
// Read and write
PHPhotoLibrary.requestAuthorization(for: .readWrite)

// Add only (save photos, no reading)
PHPhotoLibrary.requestAuthorization(for: .addOnly)
swift
// 读写权限
PHPhotoLibrary.requestAuthorization(for: .readWrite)

// 仅添加权限(保存照片,无法读取)
PHPhotoLibrary.requestAuthorization(for: .addOnly)

Limited Library Picker

受限图库选择器

swift
// 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
}
swift
// 展示选择器以扩展受限访问范围
@MainActor
func presentLimitedLibraryPicker() {
    guard let viewController = UIApplication.shared.keyWindow?.rootViewController else { return }
    PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController)
}

// 带完成回调
PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController) { identifiers in
    // identifiers: 用户添加的资源ID
}

Performing Changes

执行修改操作

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

// With completion handler
PHPhotoLibrary.shared().performChanges({
    // Changes
}) { success, error in
    // Handle result
}
swift
// 异步修改
try await PHPhotoLibrary.shared().performChanges {
    // 创建、更新或删除资源
}

// 带完成回调
PHPhotoLibrary.shared().performChanges({
    // 修改操作
}) { success, error in
    // 处理结果
}

Change Observer

修改观察者

swift
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
        }
    }
}

swift
class PhotoObserver: NSObject, PHPhotoLibraryChangeObserver {

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

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

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

        DispatchQueue.main.async {
            // 使用新的查询结果更新UI
            let newResult = changes.fetchResultAfterChanges
        }
    }
}

PHAsset

PHAsset

Represents an asset in the photo library.
表示照片库中的资源。

Fetching Assets

获取资源

swift
// 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)
swift
// 所有照片
let allPhotos = PHAsset.fetchAssets(with: .image, options: nil)

// 带选项的查询
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)

// 通过标识符查询
let assets = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil)

Asset Properties

资源属性

PropertyTypeDescription
localIdentifier
StringUnique ID
mediaType
PHAssetMediaType
.image
,
.video
,
.audio
mediaSubtypes
PHAssetMediaSubtype
.photoLive
,
.photoPanorama
, etc.
pixelWidth
IntWidth in pixels
pixelHeight
IntHeight in pixels
creationDate
Date?When taken
modificationDate
Date?Last modified
location
CLLocation?GPS location
duration
TimeIntervalVideo duration
isFavorite
BoolMarked as favorite
isHidden
BoolIn hidden album
属性类型描述
localIdentifier
String唯一ID
mediaType
PHAssetMediaType
.image
,
.video
,
.audio
mediaSubtypes
PHAssetMediaSubtype
.photoLive
,
.photoPanorama
pixelWidth
Int像素宽度
pixelHeight
Int像素高度
creationDate
Date?拍摄时间
modificationDate
Date?最后修改时间
location
CLLocation?GPS位置
duration
TimeInterval视频时长
isFavorite
Bool标记为收藏
isHidden
Bool在隐藏相册中

PHAssetMediaType

PHAssetMediaType

TypeValue
.unknown
0
.image
1
.video
2
.audio
3
类型
.unknown
0
.image
1
.video
2
.audio
3

PHAssetMediaSubtype

PHAssetMediaSubtype

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

子类型描述
.photoPanorama
全景照片
.photoHDR
HDR照片
.photoScreenshot
截图
.photoLive
Live Photo
.photoDepthEffect
人像模式
.videoStreamed
流式视频
.videoHighFrameRate
慢动作视频
.videoTimelapse
延时摄影
.videoCinematic
电影模式

PHAssetCreationRequest

PHAssetCreationRequest

Create new assets in the photo library.
在照片库中创建新资源。

Creating from UIImage

从UIImage创建

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

Creating from File URL

从文件URL创建

swift
try await PHPhotoLibrary.shared().performChanges {
    PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: imageURL)
}

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

// 视频创建
try await PHPhotoLibrary.shared().performChanges {
    PHAssetCreationRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
}

Creating with Resources

带资源参数创建

swift
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)
}
swift
try await PHPhotoLibrary.shared().performChanges {
    let request = PHAssetCreationRequest.forAsset()

    // 添加照片资源
    let options = PHAssetResourceCreationOptions()
    options.shouldMoveFile = true  // 移动文件而非复制

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

    // 设置创建日期
    request.creationDate = Date()

    // 设置位置
    request.location = CLLocation(latitude: 37.7749, longitude: -122.4194)
}

Deferred Photo Proxy (iOS 17+)

延迟照片代理(iOS 17+)

Save camera proxy photos for background processing:
swift
// 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 TypeDescription
.photo
Standard photo
.video
Video file
.photoProxy
Deferred processing proxy (iOS 17+)
.adjustmentData
Edit adjustments
保存相机代理照片以进行后台处理:
swift
// 从AVCaptureDeferredPhotoProxy回调中调用
try await PHPhotoLibrary.shared().performChanges {
    let request = PHAssetCreationRequest.forAsset()

    // 使用.photoProxy触发延迟处理
    request.addResource(with: .photoProxy, data: proxyData, options: nil)
}
资源类型描述
.photo
标准照片
.video
视频文件
.photoProxy
延迟处理代理(iOS 17+)
.adjustmentData
编辑调整数据

Getting Created Asset

获取创建的资源

swift
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
}

swift
try await PHPhotoLibrary.shared().performChanges {
    let request = PHAssetCreationRequest.forAsset()
    request.addResource(with: .photo, fileURL: url, options: nil)

    // 获取占位符用于后续查询
    let placeholder = request.placeholderForCreatedAsset
    // placeholder.localIdentifier在修改完成后可用
}

PHFetchResult

PHFetchResult

Ordered list of assets from a fetch.
查询返回的有序资源列表。

Properties

属性

PropertyTypeDescription
count
IntNumber of items
firstObject
T?First item
lastObject
T?Last item
属性类型描述
count
Int项目数量
firstObject
T?第一个项目
lastObject
T?最后一个项目

Methods

方法

swift
// 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)

swift
// 通过索引访问
let asset = fetchResult.object(at: 0)
let asset = fetchResult[0]

// 获取多个资源
let assets = fetchResult.objects(at: IndexSet(0..<10))

// 遍历
fetchResult.enumerateObjects { asset, index, stop in
    // 处理资源
    if shouldStop {
        stop.pointee = true
    }
}

// 检查是否包含
let contains = fetchResult.contains(asset)
let index = fetchResult.index(of: asset)

PHImageManager

PHImageManager

Request images from assets.
从资源中请求图片。

Request Image

请求图片

swift
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
    }
}
swift
let manager = PHImageManager.default()

let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.resizeMode = .exact
options.isNetworkAccessAllowed = true  // 允许访问iCloud照片

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 }

    // 检查是否为最终图片
    let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool) ?? false
    if !isDegraded {
        // 最终高质量图片
    }
}

PHImageRequestOptions

PHImageRequestOptions

PropertyTypeDescription
deliveryMode
PHImageRequestOptionsDeliveryModeQuality preference
resizeMode
PHImageRequestOptionsResizeModeResize behavior
isNetworkAccessAllowed
BoolAllow iCloud download
isSynchronous
BoolSynchronous request
progressHandler
BlockDownload progress
allowSecondaryDegradedImage
BoolExtra callback during deferred processing (iOS 17+)
属性类型描述
deliveryMode
PHImageRequestOptionsDeliveryMode质量偏好
resizeMode
PHImageRequestOptionsResizeMode缩放行为
isNetworkAccessAllowed
Bool允许下载iCloud照片
isSynchronous
Bool同步请求
progressHandler
Block下载进度回调
allowSecondaryDegradedImage
Bool延迟处理期间额外回调(iOS 17+)

Secondary Degraded Image (iOS 17+)

次级低质量图片(iOS 17+)

For photos undergoing deferred processing, get an intermediate quality image:
swift
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)
对于正在进行延迟处理的照片,获取中间质量的图片:
swift
let options = PHImageRequestOptions()
options.allowSecondaryDegradedImage = true

// 回调顺序:
// 1. 低质量图片(立即返回,isDegraded = true)
// 2. 中等质量图片(新特性,isDegraded = true)-- 处理中
// 3. 最终质量图片(isDegraded = false)

Delivery Modes

交付模式

ModeDescription
.opportunistic
Fast thumbnail, then high quality
.highQualityFormat
Only high quality
.fastFormat
Only fast/degraded
模式描述
.opportunistic
先返回缩略图,再返回高质量图片
.highQualityFormat
仅返回高质量图片
.fastFormat
仅返回快速/低质量图片

Request Video

请求视频

swift
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 { ... }
}

swift
manager.requestAVAsset(forVideo: asset, options: nil) { avAsset, audioMix, info in
    guard let avAsset else { return }
    // 使用AVAsset进行播放
}

// 或导出到文件
manager.requestExportSession(
    forVideo: asset,
    options: nil,
    exportPreset: AVAssetExportPresetHighestQuality
) { session, info in
    session?.outputURL = outputURL
    session?.outputFileType = .mp4
    session?.exportAsynchronously { ... }
}

PHChange

PHChange

Represents changes to the photo library.
表示照片库的修改。

Getting Change Details

获取修改详情

swift
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()
        }
    }
}

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

    // 检查修改内容
    let hasIncrementalChanges = changes.hasIncrementalChanges
    let insertedIndexes = changes.insertedIndexes
    let removedIndexes = changes.removedIndexes
    let changedIndexes = changes.changedIndexes

    // 获取新的查询结果
    let newResult = changes.fetchResultAfterChanges

    // 更新集合视图
    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

完整照片图库视图

swift
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()
        }
    }
}

swift
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
文档: /photosui/phpickerviewcontroller, /photosui/photospicker, /photos/phphotolibrary, /photos/phasset, /photos/phimagemanager
技能: axiom-photo-library, axiom-camera-capture