やりたいこと
写真ライブラリのアルバム内に画像を保存する。
指定アルバムが無ければアルバムを作成して保存する。
準備
Info.plistに使用目的を記載。
初回アクセス時のダイアログメッセージに利用されます。
Info.plist
<key>NSPhotoLibraryUsageDescription</key>
<string>画像の保存に使用します。</string>
コードTips
import
import Photos
アルバムを取得する
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", "アルバム名")
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if collection.firstObject != nil {
// 取得成功(->collection.firstObject)
} else {
// 取得失敗
}
- アルバムを作成 (クロージャー版)
アルバムを作る (クロージャー版)
PHPhotoLibrary.shared().performChanges({
let createRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: "アルバム名")
assetCollectionPlaceholder = createRequest.placeholderForCreatedAssetCollection
}) { (isSuccess, error) in
if isSuccess {
// 作成成功
} else {
// 作成失敗
}
}
- アルバムを作成 (Swift Concurrency 版)
アルバムを作る (Swift Concurrency 版)
do {
try await PHPhotoLibrary.shared().performChanges {
let createRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: "アルバム名")
assetCollectionPlaceholder = createRequest.placeholderForCreatedAssetCollection
}
// 作成成功
} catch {
// 作成失敗
}
- ファイルをアルバムに保存する (クロージャー版)
ファイルをアルバムに保存する (クロージャー版)
let album: PHAssetCollection = アルバム情報
PHPhotoLibrary.shared().performChanges({
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: fileURL)!
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album)
let placeHolder = assetRequest.placeholderForCreatedAsset
albumChangeRequest?.addAssets([placeHolder!] as NSArray)
}) { (isSuccess, error) in
if isSuccess {
// 保存成功
} else {
// 保存失敗
}
}
- ファイルをアルバムに保存する (Swift Concurrency 版)
ファイルをアルバムに保存する (Swift Concurrency 版)
let album: PHAssetCollection = アルバム情報
do {
try await PHPhotoLibrary.shared().performChanges {
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: fileURL)!
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album)
let placeHolder = assetRequest.placeholderForCreatedAsset
albumChangeRequest?.addAssets([placeHolder!] as NSArray)
}
// 保存成功
} catch {
// 保存失敗
}
コードサンプル(保存の一連処理)
- クロージャーを使って書く場合
sample_closure_version.swift
func saveImage(_ imageData: Data, toAlbum albumName: String, completionBlock: @escaping (String?) -> Void, failureBlock: @escaping (Error?) -> Void) {
let TempFilePath = "\(NSTemporaryDirectory())temp.jpg"
var imageID: String? = nil
findOrCreatePhotoAlbum(name: albumName) { (album, error) in
// 画像データを一時ファイルとして保存
let fileURL = URL(fileURLWithPath: TempFilePath)
try? imageData.write(to: fileURL, options: .atomic)
if let album = album, FileManager.default.fileExists(atPath: TempFilePath) {
PHPhotoLibrary.shared().performChanges({
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: fileURL)!
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album)
let placeHolder = assetRequest.placeholderForCreatedAsset
albumChangeRequest?.addAssets([placeHolder!] as NSArray)
imageID = assetRequest.placeholderForCreatedAsset?.localIdentifier
}) { (isSuccess, error) in
if isSuccess {
// 保存した画像にアクセスする為のimageIDを返却
completionBlock(imageID)
} else {
failureBlock(error)
}
_ = try? FileManager.default.removeItem(atPath: TempFilePath)
}
} else {
failureBlock(error)
_ = try? FileManager.default.removeItem(atPath: TempFilePath)
}
}
}
private func findOrCreatePhotoAlbum(name: String, completion: @escaping (PHAssetCollection?, Error?) -> Void) {
var assetCollection: PHAssetCollection?
var assetCollectionPlaceholder: PHObjectPlaceholder?
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", name)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if collection.firstObject != nil {
assetCollection = collection.firstObject
completion(assetCollection, nil)
} else {
PHPhotoLibrary.shared().performChanges({
let createRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: name)
assetCollectionPlaceholder = createRequest.placeholderForCreatedAssetCollection
}) { (isSuccess, error) in
if isSuccess {
let refetchResult = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [assetCollectionPlaceholder!.localIdentifier], options: nil)
assetCollection = refetchResult.firstObject
completion(assetCollection, nil)
} else {
completion(nil, error)
}
}
}
}
- Swift Concurrency を使って書く場合
sample_concurrency_version.swift
func saveImage(_ imageData: Data, toAlbum albumName: String) async throws -> String? {
let TempFilePath = "\(NSTemporaryDirectory())temp.jpg"
do {
var imageID: String? = nil
guard let album: PHAssetCollection? = try await findOrCreatePhotoAlbum(name: String) else { return nil }
guard FileManager.default.fileExists(atPath: TempFilePath) else { return nil }
try await PHPhotoLibrary.shared().performChanges {
let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: fileURL)!
let albumChangeRequest = PHAssetCollectionChangeRequest(for: album)
let placeHolder = assetRequest.placeholderForCreatedAsset
albumChangeRequest?.addAssets([placeHolder!] as NSArray)
imageID = assetRequest.placeholderForCreatedAsset?.localIdentifier
}
try? FileManager.default.removeItem(atPath: TempFilePath)
// 保存した画像にアクセスする為のimageIDを返却
return imageID
} catch {
try? FileManager.default.removeItem(atPath: TempFilePath)
throw error
}
}
private func findOrCreatePhotoAlbum(name: String) async throws -> PHAssetCollection? {
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %@", name)
let collection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
let assetCollection = collection.firstObject
if assetCollection == nil {
do {
var assetCollectionPlaceholder: PHObjectPlaceholder?
try await PHPhotoLibrary.shared().performChanges {
let createRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: name)
assetCollectionPlaceholder = createRequest.placeholderForCreatedAssetCollection
}
let refetchResult = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [assetCollectionPlaceholder!.localIdentifier], options: nil)
return refetchResult.firstObject
} catch {
throw error
}
}
return assetCollection
}
コードサンプル(保存した写真データを取得)
sample2.swift
// 保存する際に取得したimageIDを指定して画像データを取得
func getImageData(id: String, completionBlock: @escaping (Data?) -> Void) {
if let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject {
PHImageManager.default().requestImageData(for: asset, options: nil) { (data, _, _, _) in
completionBlock(data)
}
}
}
注意点
画像を保存する関数は2種類がありますが、
open class func creationRequestForAsset(from image: UIImage) -> Self
を使用して画像を保存した場合にはExifなどの情報が抜け落ちてしまう為、
元データと同じ状態で画像を保存する場合には
open class func creationRequestForAssetFromImage(atFileURL fileURL: URL) -> Self?
を利用する必要があります。
また、その際に必要なPath情報はサーバー上のURLではなく、ローカルのPath情報なので一時ファイルとして一旦ファイルを保存してそのPathを利用する必要があります。
おわり
Photos.framework導入の一助となれば幸いです。
間違っている・より良い方法がある などあれば、コメント・編集リクエスト頂けると助かります。