【SwiftUI】iOS16+のPhotosPicker|フルSwiftUIでのフォトピッカー実装方法

iOS16+の新機能によって、従来のUIKitを絡めた実装を使わずに、フルSwiftUIでフォトピッカーを実装することができるようになりました。

本記事では、SwiftUIの新しいビュー「PhotosPicker」の機能と実装方法について詳しく見ていきます。

フォトピッカー(PhotosPicker)とは

アプリケーションからユーザーの写真やビデオを選択するためのインターフェースを提供するUIコンポーネントのことを指します。

具体的には、アプリがユーザーのフォト(写真)ライブラリにアクセスして写真やビデオを選択する際に使用されているUIを提供します。

PhotosPickerを使用することで、ユーザーの写真ライブラリにアクセスし、選択された写真やビデオを取得するプロセスを簡単にすることができます。

[ 本記事はこんな人におすすめ ]

・iOS16+のフォトピッカーの実装方法を知りたい

・iOS16+の新機能を知りたい

・SwiftUIでアプリ開発をしている

iOS16+の新しいSwiftUIビュー「PhotosPicker」によって、従来の実装方法よりも簡易的にフォトピッカーを実装できるようになりました。

単一アイテムの取得、複数アイテムの取得といった基本的な機能に加えて、各種オプションによる直感的なカスタマイズ機能も用意されています。

上記の実装サンプルについては本記事最後の【ここまでの要素をまとめた実装サンプルコード】にてご紹介しています。

では、PhotosPickerの基本的な使い方から見ていきます。

PhotosPickerの基本機能

シンプルな実装コード例

PhotosPickerによってフォトピッカーを表示するシンプルな実装コードは以下のようになります。

				
					import SwiftUI
import PhotosUI // ⬅︎ ❶フレームワークをインポート

struct BasicPhotosPicker: View {
    // ❷フォトピッカー内で選択したアイテムが保持されるプロパティ
    @State var selectedItem: PhotosPickerItem?

    var body: some View {
    
        // ❸フォトピッカーを表示するビュー
        PhotosPicker(selection: $selectedItem) {
            Text("フォトピッカーを表示")
        }
    }
}
				
			

これだけの記述で、フォトピッカーを表示することができます。

基本的な実装においてのポイントは以下の三つです。

① フレームワークのインポート(PhotosUI)

今回ご紹介するPhotosPickerは、PhotosUIフレームワークにバンドル(ひとまとめ)されているため、まずはフレームワークをインポートします。

				
					import PhotosUI
				
			

② 選択アイテムを保持するプロパティ(PhotosPickerItem)

PhotosPickerItem型の状態プロパティを用意します。

ユーザーがフォトライブラリから選択した写真は、このプロパティ内に保持されます。

				
					// ❷フォトピッカー内で選択したアイテムを保持するプロパティ
@State var selectedItem: PhotosPickerItem?

// アイテムを複数枚保持する場合は配列で定義
@State var selectedItem: [PhotosPickerItem] = []
				
			

また、複数のアイテム選択を実装する場合は、PhotosPickerItemを配列で定義します。

③ ピッカーを呼び出すビュー(PhotosPicker)

ユーザーがフォトピッカーシートを呼び出すためのビューです。

				
					// ❸フォトピッカーを表示するビュー
PhotosPicker(selection: $selectedItem) {

    // <ここにピッカーを呼び出すアクションボタンのビューを定義>

}
				
			

引数「selection:」に、前述のPhotosPickerItem型プロパティを紐付けます。

また、クロージャ内では、ピッカー呼び出しボタンの外観を定義することができます。

UIImageを取得する(loadTransferable)

PhotosPickerの使用用途として、選択画像のUIImageを取得したいパターンが多いかと思います。

ピッカー内で選択した画像からUIImageを取得する方法をご紹介します。

一つの選択画像からUIImageを取得する

PhotosPickerItemのメソッドであるloadTransferableを用いることで選択アイテムをData型(※1)に変換することができます。

取得したDataをUIImage(data:)に渡すことで、UIImageを取得します。

onChangeでPhotosPickerItem型プロパティを監視しておき、アイテム選択を検知してUIImageへの変換処理を発火します。

				
					struct GetUIImagePhotoPicker: View {
    /// フォトピッカー内で選択したアイテムが保持されるプロパティ
    @State var selectedItem: PhotosPickerItem?
    /// PhotosPickerItem -> UIImageに変換したアイテムを格納するプロパティ
    @State var selectedImage: UIImage?

    var body: some View {

        PhotosPicker(selection: $selectedItem) {
            Text("フォトピッカーを表示")
        }
        // PhotosPickerItem -> Data -> UIImageに変換
        .onChange(of: selectedItem) { item in

            Task {
                guard let data = try? await item?.loadTransferable(type: Data.self) else { return }
                guard let uiImage = UIImage(data: data) else { return }
                selectedImage = uiImage
            }
        }
    }
}
				
			

loadTransferableメソッドは「async」が付与された非同期メソッドであり、さらに実行結果にエラーを返す可能性がある「throws」が付いてるため、Taskクロージャの中で「try await」を付与して実行します。

【データ変換処理の詳細】

				
					// PhotosPickerItem -> Data -> UIImageに変換
.onChange(of: selectedItem) { item in
    
    Task {
        // 選択アイテムをDataに変換(nilで処理終了)
        guard let data = try? await item?.loadTransferable(type: Data.self) else { return }
        // DataをUIImageに変換(nilで処理終了)
        guard let uiImage = UIImage(data: data) else { return }
        // UIImage型プロパティに保存
        selectedImage = uiImage
    }
}
				
			

(※1)

Data型について

Dataとは、「バイナリデータ」をコンピュータ内で保存・転送操作するために使用する構造体のことを指します。

例えば、ファイルから読み取ったデータ、ネットワークリクエストを通じて受信したデータ、画像データなど、さまざまなバイナリデータの形式をData型として扱うことができます。

以下の例は、String型の文字列をバイナリデータに変換するコード例です。

【String -> Dataに変換】

				
					// String型のデータ
let string = "Hello, World!"

// String -> Dataに変換
if let data = string.data(using: .utf8) {
    print(data)
}
				
			

また以下のように、バイナリデータとして変換した文字列を再びString型に変換し直すといったことも可能です。

【Data -> Stringに変換】

				
					// Dataに変換された文字列「"Hello, world!"」
let data: Data = ...

// Data -> Stringに変換
if let string = String(data: data, encoding: .utf8) {
    print(string) // Hello, world!
}
				
			

このように、Data構造体はバイナリデータを効果的に操作するための多くのメソッドとプロパティを持っています。

複数選択された画像のUIImageを取得する

選択アイテムを保持するPhotosPickerItemを配列で定義することで、複数のアイテム選択を実装できます。

ループ文を使って、UIImageへの変換処理が完了したアイテムを配列に格納していきます。

				
					struct GetMultipleUIImage: View {
    /// フォトピッカー内で選択した複数のアイテムが保持されるプロパティ
    @State var selectedItems: [PhotosPickerItem] = []
    /// PhotosPickerItem -> UIImageに変換した複数のアイテムを格納するプロパティ
    @State var selectedImages: [UIImage] = []

    var body: some View {

        PhotosPicker(selection: $selectedItems) {
            Text("フォトピッカーを表示")
        }
        .onChange(of: selectedItems) { items in

            Task {
                selectedImages = []

                for item in items {
                    guard let data = try await item.loadTransferable(type: Data.self) else { continue }
                    guard let uiImage = UIImage(data: data) else { continue }
                    selectedImages.append(uiImage)
                }
            }
        }
    }
}
				
			

PhotosPickerのオプション

PhotosPickerによるアイテム選択には、各種オプションを付与することができます。

主なオプションを以下に挙げます。

選択数の上限を指定(maxSelectionCount)

PhotosPickerの引数「maxSelectionCount:」を使用することで、ライブラリ内での選択アイテム最大数を指定できます。

				
					PhotosPicker(
    selection: $selectedItem,
    maxSelectionCount: 3 // ⬅︎
) {
    // ...
}
				
			

アイテム選択時のレイアウトを変更(selectionBehavior)

PhotosPickerの引数「selectionBehavior:」を使用することで、ライブラリ内で選択されたアイテムの表現パターンを指定できます。

レイアウトの種類は「.default」「.ordered」の二つです。

.ordered」を渡すと、アイテム選択順に番号が表示されるようになります。

				
					PhotosPicker(
    selection: $selectedItem,
    maxSelectionCount: 3,
    selectionBehavior: .ordered // ⬅︎
) {
    // ...
}
				
			

【defaultの場合】

選択アイテムにチェックマークが付く

【orderedの場合】

アイテムの選択順に番号が付く

ピッカーライブラリに表示されるアイテムの選定(mathing)

PhotosPickerの引数「matching:」にデータ種を指定することで、ピッカーライブラリ内に表示されるアイテムのデータ種類を選定できます。

例えば以下のように「.screenshots」を渡すと、スクリーンショットのみが抽出され、ライブラリに表示されます。

				
					PhotosPicker(
    selection: $selectedItem,
    maxSelectionCount: 3,
    selectionBehavior: .ordered,
    matching: .screenshots // ⬅︎
) {
    // ...
}
				
			

その他「画像」はもちろん、「ライブフォト」「シネマティックビデオ」など、細かな区分でデータの表示選定が可能です。

ここまでの要素をまとめた実装サンプルコード

最後に、ここまでご紹介した全ての機能を使った実装例と、全体のコードを以下に挙げます。

ピッカー内で画像が選択され、UIImage型の配列プロパティに値が格納されたら、画面に画像が表示されます。

【全体の実装コード】

				
					// 全ての機能を使ったPhotosPickerの実装まとめ

import SwiftUI
import PhotosUI

struct AllMethodsPhotosPicker: View {
    /// フォトピッカー内で選択したアイテムが保持されるプロパティ
    @State var selectedItems: [PhotosPickerItem] = []
    /// PhotosPickerItem -> UIImageに変換したアイテムを格納するプロパティ
    @State var selectedImages: [UIImage] = []

    var body: some View {

        VStack {
            // 配列内にUIImageデータが存在すれば表示
            if !selectedImages.isEmpty {
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack {
                        ForEach(selectedImages, id: \.self) { image in
                            Image(uiImage: image)
                                .resizable()
                                .scaledToFill()
                                .frame(width: 300, height: 200)
                        }
                    }
                }
            }
            // ピッカーを表示するビュー
            PhotosPicker(
                selection: $selectedItems,
                maxSelectionCount: 3,
                selectionBehavior: .ordered,
                matching: .images
            ) {
                Text("画像を取得")
            }
        }
        .onChange(of: selectedItems) { items in

            // 複数選択されたアイテムをUIImageに変換してプロパティに格納していく
            Task {
                selectedImages = []

                for item in items {
                    guard let data = try await item.loadTransferable(type: Data.self) else { continue }
                    guard let uiImage = UIImage(data: data) else { continue }
                    selectedImages.append(uiImage)
                }
            }
        }
    }
}
				
			

まとめ

以上、iOS16+の新機能「PhotosPicker」についてでした!

要点をまとめておきます。

・選択されたアイテムはPhotosPickerItemプロパティに保持される

・PhotosPickerビューによってピッカーライブラリシートを呼び出す

・loadTransferableメソッドを使って選択アイテムをDataに変換する

PhotosUIフレームワークのインポートがされていないとPhotosPickerを使用することができないので、忘れないようにしましょう。

本記事がお役に立てば幸いです。

本記事のソースコード

本記事で使用しているサンプルコードはGitHubにて公開されています。

コード内容の確認にぜひご利用ください🍎

ソースコードを見る>>

\  SHARE  /

Twitter
Facebook
Email

✏️ アプリ開発が学べる勉強会を開催中! 

CodeCandyではアプリ開発を学ぶための勉強会を定期開催しています。
学習する習慣を身につけたい、他の参加者と作業したい、アプリ開発の基本をマスターしたい、という方のために無料で学べる勉強会です。
グループにメンバー登録して頂くと、イベント開催時にメールで通知されます。

▶️グループのメンバーとして参加する

徹底した基礎学習からマスターするiPhoneアプリ開発集中オンライン講座開講!

本書「iPhoneアプリ開発集中講座」を執筆している現役エンジニア講師陣が直接に指導!
基礎、課題実習で実践力を鍛えて、オリジナルアプリ公開までチャレンジ!
充実した転職支援もあるので、エンジニアへ転職したい人にもおすすめです!

まずは、現役エンジニアに相談できる無料相談をご利用ください。

By 中川 賢亮

2022年2月よりSwift学習を始め、4月からiOSアプリ開発オンラインスクール「CodeCandy」にてアプリ開発を学ぶ。 2023年10月に個人開発アプリ「unico」をリリース。現在はアプリの機能アップデートをしながら、スクール運営の技術ブログの執筆や、出版書籍の入稿チェック・デバッグにも携わる。