WWDC Quick Look 💓 By SwiftGGTeam
What's new in the Photos picker

What's new in the Photos picker

元の動画を見る

ハイライト

iOS 17 の Photos Picker にはインラインスタイル、連続選択(continuous selection)、アクセサリ表示制御が追加され、開発者は写真ライブラリの権限を要求せずに、システムの写真選択 UI をアプリ画面にシームレスに組み込めます。

主要内容

独自の写真選択 UI を作るのは骨の折れる作業です。アルバム閲覧、検索、ズーム可能なグリッド、権限リクエストに加え、ユーザーの写真を覗き見しないことを保証する必要があります。

00:22)iOS 14 で導入されたシステム Photos Picker がこれらの課題を解決しました。独立プロセスで動作し、アプリが取得できるのはユーザーが実際に選んだ写真だけで、写真ライブラリへのアクセス権限は不要です。

しかし従来の Picker はモーダル sheet としてしか表示できませんでした。iOS 17 では Picker を SwiftUI のビュー階層に直接埋め込めるようになりました。

埋め込み Picker

02:47.photosPickerStyle(.inline) で Picker をアプリ UI に埋め込みます。引き続き独立プロセスで動作しますが、見た目はアプリと一体化します。

埋め込み Picker を初めて表示するとき、システムは自動的にオンボーディング UI を表示し、アプリがアクセスできるのは選んだ写真だけであることを伝えます。Picker にはプライバシーバッジも表示されます。

3 つのスタイル

06:11

  • .presentation:デフォルトのモーダル sheet スタイル
  • .inline:埋め込みスタイル。指定領域いっぱいに表示
  • .compact:1 行の横スクロール。スペースが限られた UI に最適

連続選択

02:40selectionBehavior: .continuous で選択をリアルタイムにアプリへ通知します。ユーザーが 1 枚選ぶたび、または解除するたびに即座に更新されます。埋め込みモードでは「追加」ボタンは不要です。

アクセサリ制御

02:11.photosPickerAccessoryVisibility でナビゲーションバー、ツールバー、サイドバーの表示を制御します。.photosPickerDisabledCapabilities で検索、アルバムナビゲーション、ステージングエリアなどを無効化し、独自 UI で置き換えられます。

Options メニュー

11:21)新しい Options メニューで、位置情報などの機密メタデータを共有するかどうかをユーザーが選べます。PhotosPicker または PHPickerViewController を使うアプリは追加作業なしで自動対応します。

詳細

埋め込み Photos Picker(SwiftUI)

02:47)Picker をアプリ UI に埋め込む例:

import SwiftUI
import PhotosUI

struct ContentView: View {
    @State private var selectedItems: [PhotosPickerItem] = []
    
    var body: some View {
        VStack {
            // 埋め込み Picker
            PhotosPicker(selection: $selectedItems,
                        selectionBehavior: .continuous) {
                // Picker 自体が内容を描画するためラベルを表示しない
            }
            .photosPickerStyle(.inline)
            .photosPickerAccessoryVisibility(.hidden, edges: .all)
            .photosPickerDisabledCapabilities([.search, .selectionActions])
            .frame(height: 300)
            .ignoresSafeArea(.container, edges: .bottom)
            
            // 選択済みの写真を表示
            SelectedPhotosList(items: selectedItems)
        }
    }
}

キーポイント:

  • .photosPickerStyle(.inline) で埋め込みモードに切り替え
  • .photosPickerAccessoryVisibility(.hidden, edges: .all) でナビゲーションバーとツールバーを非表示
  • .photosPickerDisabledCapabilities([.search, .selectionActions]) で検索と選択ボタンを無効化
  • selectionBehavior: .continuous でリアルタイム選択更新を実現
  • .ignoresSafeArea で Picker を画面下端まで延長

UIKit/AppKit での埋め込み Picker

10:02)UIKit で埋め込みモードを使う例:

import PhotosUI

class ViewController: UIViewController {
    func setupEmbeddedPicker() {
        var configuration = PHPickerConfiguration(photoLibrary: .shared())
        configuration.selection = .continuous
        configuration.mode = .compact // または .default
        configuration.edgesWithoutContentMargins = [.top, .bottom]
        configuration.disabledCapabilities = [.search]
        
        let picker = PHPickerViewController(configuration: configuration)
        addChild(picker)
        view.addSubview(picker.view)
        
        // Auto Layout で配置
        picker.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            picker.view.topAnchor.constraint(equalTo: view.topAnchor),
            picker.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            picker.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            picker.view.heightAnchor.constraint(equalToConstant: 300)
        ])
        
        picker.didMove(toParent: self)
    }
}

キーポイント:

  • PHPickerConfigurationselection.continuous に設定して連続選択を有効化
  • mode.compact にすると 1 行レイアウト
  • edgesWithoutContentMargins で指定エッジのアクセサリを非表示
  • child view controller として追加が必要

Picker 設定の動的更新

11:04)Picker 表示中に PHPickerConfiguration.Update で設定を更新:

// 選択制限を更新
var update = PHPickerConfiguration.Update()
update.selectionLimit = 5
picker.update(with: update)

// 特定のアセットの選択を解除
picker.deselectAsset(withIdentifier: assetIdentifier)

// 選択済みアセットを並べ替え
picker.moveAsset(withIdentifier: identifier, toIndex: newIndex)

キーポイント:

  • PHPickerConfiguration.Update で実行時に設定を変更可能
  • deselectAssetmoveAsset で選択済みアセットを管理

HDR とオリジナル形式の写真を受け取る

12:16)自動トランスコードを避け、オリジナル形式で取得:

import UniformTypeIdentifiers

struct PhotoPicker: View {
    @State private var selectedItems: [PhotosPickerItem] = []
    
    var body: some View {
        PhotosPicker(selection: $selectedItems,
                    matching: .images) {
            Text("写真を選択")
        }
        .onChange(of: selectedItems) { items in
            for item in items {
                // 元の形式を要求
                item.loadTransferable(type: .image,
                                     preferredItemEncoding: .current) { result in
                    switch result {
                    case .success(let data):
                        // data には元の形式の画像データが含まれます
                        break
                    case .failure(let error):
                        print(error)
                    }
                }
            }
        }
    }
}

キーポイント:

  • preferredItemEncoding: .current で自動 JPEG トランスコードを回避
  • .image タイプを要求してオリジナル形式を取得
  • .jpeg など特定タイプを要求するとトランスコードされる場合あり

Cinematic モード動画の扱い

13:24)Picker はデフォルトで Cinematic 動画のレンダリング済み版(深度効果を焼き込み)を返します。編集可能な決定点が必要な場合は写真ライブラリ権限と PhotoKit が必要です:

// Picker が返すのはレンダリング済みのビデオです
// 元の Cinematic データが必要な場合は PhotoKit を使用:

let fetchOptions = PHFetchOptions()
let assets = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: fetchOptions)

if let asset = assets.firstObject {
    // Cinematic ビデオかどうかを確認
    if asset.mediaSubtypes.contains(.videoCinematic) {
        // PHImageManager で元のビデオアセットを要求
    }
}

キーポイント:

  • Picker から返る Cinematic 動画はレンダリング済み版
  • 編集可能版には PhotoKit 権限が必要
  • 「Support Cinematic mode videos in your app」セッションを参照

アクセサリ表示制御

04:52)Picker の UI 要素を制御:

// すべてのアクセサリを非表示
.photosPickerAccessoryVisibility(.hidden, edges: .all)

// 下部ツールバーだけを非表示
.photosPickerAccessoryVisibility(.hidden, edges: .bottom)

// iPadOS/macOS ではサイドバーを非表示
.photosPickerAccessoryVisibility(.hidden, edges: .leading)

アクセサリの定義:

  • iOS:上部はナビゲーションバー、下部はツールバー
  • iPadOS/macOS:左側はサイドバー、上下は iOS と同様

Picker 機能の無効化

05:12)特定機能を無効化:

.photosPickerDisabledCapabilities([.search, .collectionNavigation, .stagingArea])

無効化できる機能:

  • .search:検索バーを非表示
  • .collectionNavigation:アルバムタブとサイドバーを非表示
  • .stagingArea:ツールバーボタンをステータスラベルに置換
  • .selectionActions:「キャンセル」「追加」ボタンを非表示(.continuous 選択と併用)

重要ポイント

1. ソーシャルアプリ向けにシームレスな写真選択体験を構築

やること:投稿やコメント画面に Photos Picker を直接埋め込み、選択した写真を入力欄の下にリアルタイム表示する。

価値:従来の sheet モードは投稿フローを中断します。埋め込み Picker なら写真選択と文字編集を同時に進められ、体験が滑らかになります。

始め方:.photosPickerStyle(.inline) で Picker を埋め込み、selectionBehavior: .continuous でリアルタイム更新を設定し、ナビゲーションバーとツールバーを非表示にしてアプリデザインに馴染ませる。

2. 写真編集アプリにコンパクト選択バーを追加

やること:編集ツールバーの上にコンパクトな横スクロールの写真選択バーを置き、編集対象の写真を素早く切り替えられるようにする。

価値:.compact スタイルは 1 行の高さだけ。スペースが限られたツール UI に最適。編集画面を離れずに新しい写真を選べます。

始め方:PHPickerConfigurationmode: .compact を使い、小さな固定高さを設定し、選択変更を監視して新しい画像を読み込む。

3. プライバシー優先の写真管理アプリを構築

やること:写真ライブラリ全体の権限を要求せず、Photos Picker のみを使い、ユーザーが共有する写真を正確に制御できるようにする。

価値:Options メニューで位置などの機密メタデータを除去できます。独立プロセス設計により、アプリは選ばれた写真にしかアクセスできず、ユーザーの信頼を築けます。

始め方:Photos Picker の Transferable API で画像を受け取り、preferredItemEncoding: .current でオリジナル形式と HDR 情報を保持。UIImagePickerController や写真ライブラリ全体の権限要求は避ける。

4. ギャラリーアプリにカスタムアルバムナビゲーションを追加

やること:Picker 内蔵のアルバムナビゲーションを無効化し、独自のサイドバーやドロップダウンでアルバムを切り替えつつ、グリッドと検索は Picker に任せる。

価値:プロジェクト別やタイムライン別など特殊なアルバム構成に対応でき、全体の情報設計に合ったナビゲーションを実現できます。

始め方:.photosPickerDisabledCapabilities(.collectionNavigation) で内蔵ナビゲーションを非表示にし、Picker の横にカスタムアルバム一覧を配置し、PhotoKit でアルバム内容を渡す。

関連セッション

コメント

GitHub Issues · utterances