关于 axiom-photo-library-ref
面向 iOS 开发者的 Apple PhotosUI 和 PhotoKit API 参考技能,涵盖照片选择、图库权限、资源查询与修改,以及图像和视频读取。
主要功能
- 01使用 SwiftUI 和 UIKit 照片选择器
- 02设置筛选条件和选择数量
- 03请求并检查图库访问权限
- 04查询和修改照片资源
- 05读取图像与视频资源
适用场景
- 01让用户选择照片或视频
- 02管理照片图库授权
- 03按类型或日期查询资源
- 04将图像和视频保存到图库
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-photo-library-ref安装后在 Claude Code 中输入/axiom-photo-library-ref确认已加载。
详细介绍
照片图库 API 参考
快速参考
// 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+)
适用于 UIKit 应用的系统照片选择器。无需权限。
配置
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
筛选选项
// 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])
展示
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
present(picker, animated: true)
代理
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 属性
| 属性 | 类型 | 描述 |
|---|---|---|
itemProvider | NSItemProvider | 提供所选资源的数据 |
assetIdentifier | String? | PHAsset 标识符(使用 photoLibrary 配置时) |
PhotosPicker(SwiftUI,iOS 16+)
用于选择照片的 SwiftUI 视图。无需权限。
基本用法
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")
}
筛选器
// 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)])
选择行为
PhotosPicker(
selection: $items,
maxSelectionCount: 10,
selectionBehavior: .ordered, // .default, .ordered, .continuous
matching: .images
) { ... }
| 行为 | 描述 |
|---|---|
.default | 标准多选 |
.ordered | 保留选择顺序 |
.continuous | 用户选择时实时更新(iOS 17+) |
内嵌选择器(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)
| 样式 | 描述 |
|---|---|
.presentation | 模态工作表(默认) |
.inline | 内嵌在视图中 |
.compact | 单行 |
| 禁用的功能 | 效果 |
|---|---|
.search | 隐藏搜索栏 |
.collectionNavigation | 隐藏相簿 |
.stagingArea | 隐藏选择项预览 |
.selectionActions | 隐藏“添加”/“取消” |
| 附件可见性 | 描述 |
|---|---|
.hidden, .automatic, .visible | 按边缘设置 |
保留 HDR(iOS 17+)
PhotosPicker(
selection: $items,
matching: .images,
preferredItemEncoding: .current // Don't transcode, preserve HDR
) { ... }
| 编码 | 描述 |
|---|---|
.automatic | 由系统决定格式 |
.current | 原始格式,保留 HDR |
.compatible | 强制使用兼容格式 |
从 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 属性
| 属性 | 类型 | 描述 |
|---|---|---|
itemIdentifier | String | 唯一标识符 |
supportedContentTypes | [UTType] | 可用的表示形式 |
PhotosPickerItem 方法
// 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
PHPhotoLibrary
访问和修改照片图库。
授权状态
// Check current status
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
// Request authorization
let newStatus = await PHPhotoLibrary.requestAuthorization(for: .readWrite)
PHAuthorizationStatus
| 状态 | 描述 |
|---|---|
.notDetermined | 尚未询问用户 |
.restricted | 家长控制限制访问 |
.denied | 用户拒绝访问 |
.authorized | 已授予完全访问权限 |
.limited | 仅可访问用户选择的照片(iOS 14+) |
访问级别
// Read and write
PHPhotoLibrary.requestAuthorization(for: .readWrite)
// Add only (save photos, no reading)
PHPhotoLibrary.requestAuthorization(for: .addOnly)
受限图库选择器
// 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
}
执行更改
// Async changes
try await PHPhotoLibrary.shared().performChanges {
// Create, update, or delete assets
}
// With completion handler
PHPhotoLibrary.shared().performChanges({
// Changes
}) { success, error in
// Handle result
}
更改观察器
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
表示照片图库中的一个资源。
获取资源
// 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)
资源属性
| 属性 | 类型 | 描述 |
|---|---|---|
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
| 类型 | 值 |
|---|---|
.unknown | 0 |
.image | 1 |
.video | 2 |
.audio | 3 |
PHAssetMediaSubtype
| 子类型 | 描述 |
|---|---|
.photoPanorama | 全景照片 |
.photoHDR | HDR 照片 |
.photoScreenshot | 屏幕截图 |
.photoLive | 实况照片 |
.photoDepthEffect | 人像模式 |
.videoStreamed | 流媒体视频 |
.videoHighFrameRate | 慢动作视频 |
.videoTimelapse | 延时摄影 |
.videoCinematic | 电影效果模式 |
PHAssetCreationRequest
在照片图库中创建新资源。
从 UIImage 创建
try await PHPhotoLibrary.shared().performChanges {
PHAssetCreationRequest.creationRequestForAsset(from: image)
}
从文件 URL 创建
try await PHPhotoLibrary.shared().performChanges {
PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: imageURL)
}
// For video
try await PHPhotoLibrary.shared().performChanges {
PHAssetCreationRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
}
使用资源创建
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)
}
延迟照片代理(iOS 17+)
保存相机代理照片,以便在后台处理:
// 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)
}
| 资源类型 | 描述 |
|---|---|
.photo | 标准照片 |
.video | 视频文件 |
.photoProxy | 延迟处理代理(iOS 17+) |
.adjustmentData | 编辑调整数据 |
获取已创建的资源
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
}
自定义相簿
// Create a custom album
func getOrCreateAlbum(named title: String) async throws -> PHAssetCollection {
// Check if album already exists
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", title)
let existing = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let album = existing.firstObject { return album }
// Create new album
var placeholder: PHObjectPlaceholder?
try await PHPhotoLibrary.shared().performChanges {
let request = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: title)
placeholder = request.placeholderForCreatedAssetCollection
}
guard let id = placeholder?.localIdentifier,
let album = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [id], options: nil).firstObject
else { throw PhotoError.albumCreationFailed }
return album
}
// Save photo to custom album
func saveToAlbum(_ image: UIImage, album: PHAssetCollection) async throws {
try await PHPhotoLibrary.shared().performChanges {
let assetRequest = PHAssetCreationRequest.creationRequestForAsset(from: image)
guard let placeholder = assetRequest.placeholderForCreatedAsset,
let albumRequest = PHAssetCollectionChangeRequest(for: album) else { return }
albumRequest.addAssets([placeholder] as NSFastEnumeration)
}
}
PHFetchResult
从获取结果中返回的有序资源列表。
属性
| 属性 | 类型 | 描述 |
|---|---|---|
count | Int | 项目数量 |
firstObject | T? | 第一项 |
lastObject | T? | 最后一项 |
方法
// 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
从资源中请求图像。
请求图像
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
| 属性 | 类型 | 描述 |
|---|---|---|
deliveryMode | PHImageRequestOptionsDeliveryMode | 质量偏好 |
resizeMode | PHImageRequestOptionsResizeMode | 调整大小的行为 |
isNetworkAccessAllowed | Bool | 允许从 iCloud 下载 |
isSynchronous | Bool | 同步请求 |
progressHandler | Block | 下载进度 |
allowSecondaryDegradedImage | Bool | 延迟处理期间的额外回调(iOS 17+) |
次级降质图像(iOS 17+)
对于正在进行延迟处理的照片,可获取中间质量的图像:
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)
交付模式
| 模式 | 描述 |
|---|---|
.opportunistic | 先提供快速缩略图,再提供高质量图像 |
.highQualityFormat | 仅提供高质量图像 |
.fastFormat | 仅提供快速/降质图像 |
请求视频
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
表示照片图库中的更改。
获取更改详情
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()
}
}
}
常见代码模式
完整的照片图库视图
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()
}
}
}
资源
文档:/photosui/phpickerviewcontroller, /photosui/photospicker, /photos/phphotolibrary, /photos/phasset, /photos/phimagemanager
技能:axiom-photo-library, axiom-camera-capture
评论
支持 Markdown