【Swift/PhotoKit】PHAssetとは?モデルオブジェクトの取得と操作方法!
この記事からわかること
- PhotoKitとは?
- モデルオブジェクトの取得と操作方法
- PHPhotoLibraryクラスとは?
- PHAssetの使い方
- アセットを取得(フェッチ)する方法
- PHFetchResultクラスからオブジェクトを取り出す
enumerateObjects
メソッドの使い方 - 画像(
UIImage
)を取得する方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
参考文献:Apple-PhotoKit
参考文献:Fetching Objects and Requesting Changes
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
の機能も使用できるようになります。
今回はPHAssetなどのモデルオブジェクトに焦点を当ててまとめていきます。
モデルオブジェクト
PhotoKitではフォトライブラリ内のデータを操作するためのAPIとしてモデルオブジェクトが複数用意されています。写真アプリ内のデータ構造をモデルオブジェクトにコピーして操作していくイメージです。
- PHAsset:画像、動画、Live Photos
- PHAssetCollection:アルバム or モーメント
- PHCollectionList:アルバムフォルダー or モーメントクラスター
上記のようにPhotoKitでは写真アプリ内の画像や動画などはPHAssetオブジェクトとして、アルバムはPHAssetCollectionオブジェクトとして操作できるようになっています。
またこれらのインスタンスは読み取り専用のimmutable(不変)で、メタデータのみを含み、モデルオブジェクトに対してクエリを構築し情報をフェッチします。
オブジェクトを変更したい場合は変更要求オブジェクト(performChangesメソッド)を構築して、明示的に共有オブジェクト(PHPhotoLibrary)にコミットする必要があります。
おすすめ記事:【Swift/PhotoKit】デバイスに写真を保存・削除・更新する方法!
PHPhotoLibraryクラス
PHPhotoLibrary
クラスはユーザーの写真ライブラリへのアクセスと変更を管理するオブジェクトです。shared
メソッドから共有されるシングルトンのインスタンスを参照します。
class PHPhotoLibrary : NSObject
参照はシングルトンのインスタンスから
PHPhotoLibrary.shared()
役割
- 写真アプリへのアクセス許可をユーザーに申請する
- アセットとコレクションに変更する
- ライブラリの更新を検知する
- 更新メッセージの登録
PHAssetクラス
PHAssetクラスはフォトライブラリ内の画像や動画、Live Photoなどのモデルオブジェクトでした。保持しているプロパティは以下の通りです。
class PHAsset : PHObject {
var mediaType: PHAssetMediaType // ビデオやオーディオなどのアセットのタイプ
var mediaSubtypes: PHAssetMediaSubtype // アセットのサブタイプ パノラマ写真や高フレームレートのビデオなど、特別な種類のアセットを識別
var sourceType: PHAssetSourceType // アセットがフォトライブラリに入る手段
var pixelWidth: Int // アセットデータの幅
var pixelHeight: Int // アセットデータの高さ
var creationDate: Date? // アセットの作成日時。
var modifiedDate: Date? // アセットが最後に変更された日時
var location: CLLocation? // アセットの位置情報。
var duration: TimeInterval // 動画アセットの長さ (秒単位)
var isFavorite: Bool // お気に入り
var isHidden: Bool // 非表示
var hasAdjustments: Bool // アセットに調整データが含まれているかどうか
var adjustmentFormatIdentifier: NSString? // 調整フォーマットの識別子
}
このPHAssetを取得する方法を見ていきます。
アセットを取得(フェッチ)する方法
アセットを取得(フェッチ)するにはPHAssetの持つfetchAssets
メソッドを使用します。同名の引数違いで様々なメソッドが定義されていますが返り値がPHFetchResult<PHAsset>
型になるのは共通です。
例えば指定したメディアタイプ(画像や動画など)のみのアセットを取得する場合は以下のようになります。
let fetchedImageResult:PHFetchResult = PHAsset.fetchAssets(with: .image , options: nil)
アセットコレクション内のアセットを取得
let fetchedCollectionResult:PHFetchResult = PHAsset.fetchAssets(in: assetCollection , options: nil)
識別子にマッチするアセットを取得
let fetchedIdentifierResult:PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: nil)
またフェッチにはPHFetchOptions型のオプションを渡すことができ順序のソートやフェッチする最大数を指定できます。
PHFetchResultクラス
PHFetchResultクラスはフェッチ結果を提供するクラスです。配列の要素にフェッチしたPHAsset、PHCollection,、PHAssetCollection、PHCollectionListのいずれかのオブジェクトを保持します。
class PHFetchResult<ObjectType> : NSObject where ObjectType : AnyObject
保持する個数に参照できるcount
プロパティや存在するかどうかをチェックするcontains
メソッド、最初のオブジェクトを参照するfirstObject
プロパティなどが用意されています。
今回は保持しているオブジェクトを全てに特定のクロージャを実行できるenumerateObjects
を使用します。
PHFetchResult.enumerateObjects { (asset, index , pointer ) in
//
}
クロージャーの中では対象のオブジェクト、インデックス、取り出しを停止させるBool値の3つの引数を取得できます。
画像(UIImage)を取得する方法
ここで一度PHAssetを使ってフォトライブラリの画像を取得しビューとして表示させる流れをみていきます。
- info.plistにキーを追加
- UIの構築
- PHAssetのフェッチ
- 画像(UIImage)の取得
info.plistにキーを追加
PhotoKitを使用してアセットやコレクションの取得、ライブラリの更新など、アプリがPhotoKitの高度な機能を使用するためにはアプリ内からデバイスの写真アプリにアクセスできるように「info.plist」に「NSPhotoLibraryUsageDescription」キーを追加する必要があります。
UIの構築
続いて必要となるUIを準備します。ViewController
クラスには表示用のUIImageView
とフォトライブラリのアセットを保持する用のプロパティを定義しておきます。またアセットを取得してセットするボタンと実際にビューに反映させるボタンも配置しておきます。
import UIKit
import PhotosUI
class PhotoKitViewController: UIViewController {
let imageView = UIImageView() // 表示用View
var photoAssets: [PHAsset] = [] // フォトライブラリ保持用
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.backgroundColor = .orange
button.frame = CGRect(x: UIScreen.main.bounds.width/3, y: 200, width: UIScreen.main.bounds.width/3, height: 50)
button.setTitle("Assetsを取得", for: .normal)
button.addTarget(self, action: #selector(setAssets), for: .touchUpInside)
view.addSubview(button)
let button2 = UIButton()
button2.backgroundColor = .brown
button2.frame = CGRect(x: UIScreen.main.bounds.width/3, y: 300, width: UIScreen.main.bounds.width/3, height: 50)
button2.setTitle("画像を取得", for: .normal)
button2.addTarget(self, action: #selector(showImage), for: .touchUpInside)
view.addSubview(button2)
imageView.frame = CGRect(x: 0, y: 400, width: UIScreen.main.bounds.width, height: 300)
view.addSubview(imageView)
}
@objc func setAssets() {
//
}
@objc func showImage(){
//
}
}
PHAssetのフェッチ
まずは現在のフォトライブラリをPhotoKitで操作できる形(アセット)になるようにPHAsset.fetchAssets
メソッドを使ってフェッチします。今回はオプションは指定なしにしておきました。またこのメソッドを呼び出した際に許可申請アラートが表示されます。取得できるのはPHFetchResult
型のデータです。
@objc func setAssets() {
// let fetchOptions = PHFetchOptions()
let assets: PHFetchResult = PHAsset.fetchAssets(with: .image, options: nil)
assets.enumerateObjects { (asset, _ , _) in
self.photoAssets += [asset]
}
}
取得したPHFetchResultからをenumerateObjects
メソッドを使用してphotoAssets
プロパティに1つずつオブジェクト(PHAsset)を格納していきます。
画像(UIImage)の取得
PHAssetが取得できればPHImageManager
を使用してUIImageを取得するだけです。PHImageManager
の使用方法やリクエストオプションの概要は以下の記事を参考にしてください。
おすすめ記事:【Swift/PhotoKit】PHImageManagerの使い方!アセット操作と画像の取得
@objc func showImage(){
let manager: PHImageManager = PHImageManager()
guard let firstAsset = photoAssets.first else {
return
}
let requestOptions = PHImageRequestOptions()
requestOptions.version = PHImageRequestOptionsVersion.current
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryMode.highQualityFormat
requestOptions.resizeMode = PHImageRequestOptionsResizeMode.exact
requestOptions.isSynchronous = true
requestOptions.isNetworkAccessAllowed = true
manager.requestImage(for: firstAsset,
targetSize: imageView.frame.size,
contentMode: PHImageContentMode.aspectFill,
options: requestOptions) { (image, info) in
DispatchQueue.main.async {
self.imageView.image = image
}
}
}
カメラロールから取得した画像のメタデータを取得する方法
PHPickerViewController
を使用してアルバムから写真を取得している場合に写真のメタデータ(撮影日時や撮影地など)を取得するにはいくつか注意点がありました。詳細は以下の記事を参考にしてください。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。