WWDC Quick Look 💓 By SwiftGGTeam
Share files with SharePlay

Share files with SharePlay

元の動画を見る

ハイライト

iOS 17 で追加された GroupSessionJournal API により、SharePlay App は最大 100MB の添付ファイル(画像、PDF、音声など)を転送できます。Apple のクラウド基盤を使ってエンドツーエンド暗号化された高速転送を実現し、遅れて参加した人の状態同期も自動処理します。

主要内容

GroupSessionMessenger の限界

SharePlay App はこれまで GroupSessionMessenger で状態を同期していましたが、これは小さなメッセージデータの転送に向いています。共同描画 App を想像してください。筆跡は Messenger で送れますが、ユーザーがキャンバスに写真をドラッグした場合、この経路では対応できません。

00:58GroupSessionJournal はこの空白を埋めます。FaceTime と同じ Apple のクラウド基盤を使い、複数の技術を組み合わせて転送データ量と待ち時間を減らし、全工程でエンドツーエンド暗号化されます。

Journal の仕組み

02:14GroupSessionJournal は 1 つの GroupSession に紐づき、すべての参加者が同じ Journal インスタンスを共有します。誰かがローカルで add を呼ぶと、全員の attachments AsyncSequence が新しい添付ファイルイベントを受け取り、remove を呼ぶと全員の環境で同期して削除されます。

添付ファイルの型は Transferable プロトコルに準拠するだけでよく、残りの転送ロジックはシステムが処理します。

2 つの重要な設計制約

03:37)添付ファイルのサイズ上限は 100MB です。これにより、開発者はコンテンツタイプを切り分ける必要があります。ユーザーが生成した写真、PDF、ボイスメモは Journal に適していますが、映画のような大きなファイルは App 独自のサーバーを使うべきです。

04:18)ライフサイクルは GroupSession に紐づきます。誰かがオンラインでいる限り添付ファイルは保持され、全員が離れると自動的に削除されます。つまり重要なデータは Session 終了前にローカルへ永続化する必要があります。

遅れて参加した人の問題を自動解決

04:47)以前は新しく参加した人のために、他の参加者が状態データを再送信する必要がありました。状態に大きな添付ファイルが含まれている場合、全員がもう一度アップロードする必要がありました。Journal は既存の添付ファイルを新しい参加者へ自動同期するため、他の人が再アップロードする必要はありません。

詳細

DrawTogether に Journal を統合する

Session では DrawTogether App を例に、4 ステップの統合を示しました。

ステップ 1: データモデルを定義する

Journal 内の画像を追跡する CanvasImage 構造体を作成します。

struct CanvasImage: Identifiable {
    let id: UUID
    let image: UIImage
    var position: CGPoint
}

ステップ 2: Canvas モデル内で Journal を作成する

class Canvas: ObservableObject {
    @Published var images: [CanvasImage] = []
    @Published var selectedImageData: Data?

    private var journal: GroupSessionJournal?

    func configureGroupSession(_ session: GroupSession<DrawTogether>) {
        // Journal を作成
        journal = GroupSessionJournal(session: session)

        // 添付ファイルの変更を監視
        Task {
            for await attachment in journal!.attachments {
                await handleAttachment(attachment)
            }
        }
    }

    func addImageToJournal(_ imageData: Data) {
        guard let journal = journal else { return }

        // Transferable 添付ファイルを作成してアップロード
        let attachment = TransferableAttachment(data: imageData, type: .image)
        journal.add(attachment)
    }
}

キーポイント:

  • GroupSessionJournal(session:) は GroupSession で初期化する
  • attachments は AsyncSequence で、for await を使って新しい添付ファイルを監視する
  • add() で添付ファイルをアップロードすると、すべての参加者が受信する

ステップ 3: 画像選択ボタンを追加する

import PhotosUI

struct ControlBar: View {
    @ObservedObject var canvas: Canvas

    var body: some View {
        HStack {
            // その他のボタン...

            PhotosPicker(selection: $canvas.selectedImageData,
                         matching: .images) {
                Image(systemName: "photo")
            }
        }
    }
}

ステップ 4: キャンバスに画像を表示する

struct CanvasView: View {
    @ObservedObject var canvas: Canvas

    var body: some View {
        ZStack {
            // ストロークを描画...

            // すべての画像を表示
            ForEach(canvas.images) { canvasImage in
                Image(uiImage: canvasImage.image)
                    .position(canvasImage.position)
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                canvasImage.position = value.location
                            }
                    )
            }
        }
    }
}

キーポイント:

  • PhotosPicker は SwiftUI の組み込みコンポーネントで、iOS 16 以降で利用できる
  • 画像位置のドラッグは DragGesture で実装する
  • すべての画像状態は Journal を通じて全参加者へ同期される

添付ファイル処理フロー

func handleAttachment(_ attachment: GroupSessionJournal.Attachment) async {
    do {
        let data = try await attachment.load(Data.self)
        if let image = UIImage(data: data) {
            let canvasImage = CanvasImage(
                id: attachment.id,
                image: image,
                position: .center
            )
            await MainActor.run {
                images.append(canvasImage)
            }
        }
    } catch {
        print("Failed to load attachment: \(error)")
    }
}

キーポイント:

  • attachment.load(Data.self) は添付ファイルデータを非同期で読み込む
  • 読み込み完了後、メインスレッドに戻って UI を更新する
  • 添付ファイル ID は後続の削除操作に利用できる

重要ポイント

  1. 画像付き共同作業ホワイトボード App

    • 何を作るか: 複数人が同じキャンバスに画像を貼り、注釈を付け、議論できる
    • なぜ価値があるか: Journal が画像転送と同期を自動処理するため、開発者はキャンバスのインタラクションロジックに集中できる
    • 始め方: GroupSessionJournal で画像添付ファイルを転送し、GroupSessionMessenger で筆跡を同期する
  2. 共有アルバム付きイベント App

    • 何を作るか: イベントや旅行中にグループで写真をリアルタイム共有し、全員が互いの撮ったばかりの写真を見られる
    • なぜ価値があるか: Journal のエンドツーエンド暗号化と自動同期により、写真共有を安全かつ即時にできる
    • 始め方: 写真選択後に journal.add() でアップロードし、attachments シーケンスを監視して他の人の写真を受け取る
  3. 共同注釈付きドキュメントレビュー ツール

    • 何を作るか: チームメンバーが PDF ドキュメントを一緒にレビューし、それぞれ注釈やハイライトを追加する
    • なぜ価値があるか: PDF ファイルは通常 100MB 未満で Journal に適しており、注釈データは Messenger で送ることで完全な体験を実現できる
    • 始め方: PDF ファイルを Journal 添付ファイルとして転送し、注釈の座標とテキストを Messenger でリアルタイム同期する
  4. 共有プレイリスト付き音楽 App

    • 何を作るか: グループで 1 つのプレイリストを共同編集し、誰でも曲を追加できる
    • なぜ価値があるか: 音声ファイル本体は Journal に載せないが、アルバムアートやユーザーメモなどのメタデータは Journal で高速同期できる
    • 始め方: Journal でカバー画像とプレイリスト変更を同期し、Messenger で再生状態を同期する

関連セッション

コメント

GitHub Issues · utterances