【Swift/PhotoKit】デバイスに写真を保存・削除・更新する方法!
この記事からわかること
- PhotoKitとは?
- デバイスに写真を保存・削除・変更する方法
- performChangesメソッドの使い方
- performChangesAndWaitとの違い
- PHAssetChangeRequestクラスとは?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
参考文献:Apple-PhotoKit
参考文献:Fetching Objects and Requesting Changes
環境
- Xcode:15.0.1
- watchOS:10.0
- Swift:5.9
- macOS:Sonoma 14.1
PhotoKitとは?
そもそもPhotoKitとはApple製のデバイスに入っている「写真アプリ」で管理されている写真や動画を操作するためのAPIを提供している技術です。実際の中身はPhotos FrameworkとPhotosUI Frameworkの2つのフレームワークに分けられて構成されており、それぞれをimportすることで使用できるようになります。
import Photos
import PhotosUI
Photos
カメラロール(フォトライブラリ)を管理するオブジェクトを提供するPHPhotoLibrary
や、実際の画像や動画などのアセットを管理するPHAsset
、アセットのコレクション(アルバムなど)を表現するPHAssetCollection
などといった基本的な写真アプリ操作機能を提供。
PhotosUI
画像のピッカービューを構築するためのPHPickerViewController
やその構成を定義するPHPickerConfiguration
などUIに関する機能を提供。
またPhotosUI
の中にPhotos
が含まれているためPhotosUI
のみでPhotos
の機能も使用できるようになります。
今回はPhotoKitを使用してデバイスに写真を保存・削除・更新する方法をまとめていきます。
前準備:info.plistにキーを追加
PhotoKitを使用してアセットやコレクションの取得、ライブラリの更新など、アプリがPhotoKitの高度な機能を使用するためにはアプリ内からデバイスの写真アプリにアクセスできるように「info.plist」に「NSPhotoLibraryUsageDescription」キーを追加する必要があります。
immutable(不変)なモデルオブジェクト
そもそもPhotoKitではフォトライブラリ内の画像や動画などを操作しやすいように適したモデルオブジェクト(PHAssetなど)に変換して操作しています。
おすすめ記事:【Swift/PhotoKit】PHAssetとは?モデルオブジェクトの取得と操作方法!
しかしこれらのモデルオブジェクトは読み取り専用のimmutable(不変)であり、取得やUIImageとして表示するだけなら可能ですが操作(削除や変更)することができません。
変更を可能にするためには変更要求リクエストを構築して明示的に共有オブジェクト(PHPhotoLibrary)にコミットする必要があります。
performChangesメソッドで変更要求リクエストの構築
変更要求リクエストはPHPhotoLibrary
クラスのperformChanges
メソッドを使用して構築します。
func performChanges(
_ changeBlock: @escaping () -> Void,
completionHandler: ((Bool, Error?) -> Void)? = nil
)
例えばアセットをお気に入りに登録する場合は以下のようになります。ここではPHPhotoLibrary
インスタンスから呼び出したperformChanges
メソッド内でリクエスト(PHAssetChangeRequest)を生成しアセットを更新しているのがポイントです。そしてこの変更は実際にデバイスの写真アプリ内にある対象の画像にも影響します。
PHPhotoLibrary.shared().performChanges {
let request = PHAssetChangeRequest(for: asset)
request.isFavorite = !asset.isFavorite
} completionHandler: { success, error in
print("Finished updating asset. " + (success ? "Success." : error!.localizedDescription))
}
またperformChanges
メソッドは非同期で実行されますが同期的に実行されるperformChangesAndWait
メソッドも用意されています。使用方法は基本的に同じです。
公式リファレンス:performChangesAndWait
PHAssetChangeRequestクラス
公式リファレンス:PHAssetChangeRequestクラス
PHAssetChangeRequest
はperformChanges
内で使用するアセットのメタデータの作成、削除、変更、またはコンテンツの編集を要求するクラスです。
プロパティからアセットのメタデータを参照でき、任意の値を格納することで変更できます。
class PHAssetChangeRequest : PHChangeRequest {
var creationDate: Date? // 作成日時
var location: CLLocation? // 位置情報
var isFavorite: Bool // お気に入りマークされているかどうか
var isHidden: Bool // アセットがコレクションで非表示になっているか
}
アセットの保存(UIImage)
UIImageとして保持しているデータをアセットとして新規で保存する場合はcreationRequestForAsset
メソッドを使用します。引数に保存したい画像(UIImage型)を渡します。以下のサンプルはSF-Symbolをフォトライブラリに保存しています。
PHPhotoLibrary.shared().performChanges {
PHAssetChangeRequest.creationRequestForAsset(from: UIImage(systemName: "iphone")!)
} completionHandler: { success, error in
print("Finished updating asset. " + (success ? "Success." : error!.localizedDescription))
}
UIImageから保存したアセットにはメタデータが欠落している可能性があります。元データを同じ情報を保持したアセットを作成したい場合はcreationRequestForAssetFromImage
を使用します。
アセットの保存(ファイルパス)
creationRequestForAssetFromImage
メソッドは指定したファイルパスからアセットを保存するメソッドです。
PHPhotoLibrary.shared().performChanges {
PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: fileURL!))
} completionHandler: { success, error in
print("Finished updating asset. " + (success ? "Success." : error!.localizedDescription))
}
ファイルパス(URL)として指定できるのはサーバーのURLではなくローカル(デバイス内)のファイルパスのようです。サーバーのURLを指定した場合は以下のようなエラーが発生し保存することができませんでした。
The operation couldn’t be completed. (PHPhotosErrorDomain error -1.)
アセットの削除
アセットを削除するにはdeleteAssets
メソッドを使用します。引数には削除したいアセットを配列形式で指定します。
PHPhotoLibrary.shared().performChanges {
guard let lastAsset = self.photoAssets.last else{ return }
PHAssetChangeRequest.deleteAssets(NSArray(array: [lastAsset]))
} completionHandler: { success, error in
print("Finished updating asset. " + (success ? "Success." : error!.localizedDescription))
}
このメソッドを使用して削除を実行しようとすると以下のような確認アラートが表示されます。
おすすめ記事:【Swift/PhotoKit】画像ファイル名を指定して保存する方法!DCF規格とは?
連続で複数枚の写真を保存する
一度の処理で配列形式でUIImage
を渡し連続でカメラに保存する処理を実装してみましたが、途中でError Domain=PHPhotosErrorDomain Code=3303 "(null)"
という詳細不明のエラーが発生し失敗してしまいました。
func savePhotosToAlbum(_ images: [UIImage], completion: @escaping (Bool, Error?) -> Void) {
PHPhotoLibrary.shared().performChanges {
for image in images {
let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
}
} completionHandler: { success, error in
completion(success, error)
}
}
上記の実装ではダメだったので1枚保存するようにしてcompletionHandler
で結果を受け取ってから次の画像を保存するように実装してみましたが、時間がかかりすぎる上にたまに失敗してしまったので一度に大量の画像を保存することはできないのかもしれません。
画像(UIImage)をカメラロールに保存する方法
Swiftで画像(UIImage)をカメラロールに保存する方法は2種類あります。活用する場面によって適切な方を選択して使用することをおすすめします。
UIImageWriteToSavedPhotosAlbumメソッド
- グローバル関数として定義
- 1行のコードで画像を保存
- 完了ハンドラーが提供されていない
- 完了セレクターから結果を取得
- 写真ライブラリへのアクセス権限が必要
PhotoKitのPHPhotoLibrary
- iOS8以降で利用可能なフレームワーク
- 保存の進行状況を監視できる完了ハンドラーが提供
- アルバムへの保存やメタデータの変更など、より高度な操作が可能
- PHAuthorizationStatusを使用して許可の確認が可能
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。