WWDC Quick Look 💓 By SwiftGGTeam
Enhance child safety with PermissionKit

Enhance child safety with PermissionKit

元の動画を見る

ハイライト

PermissionKit が解こうとしている問題は非常に具体的です。app 内で子どもが見知らぬ相手と通信を始めようとしたとき、app はどのように安全に保護者の許可を取得すればよいのか、というものです。

主要内容

子どもが app で初めて見知らぬ相手からのメッセージを受け取る、というのは、あらゆる年齢層を対象にしたソーシャル、チャット、ゲーム app が必ず直面する難題です。開発者には次のような仕組みが必要になります。子どもが保護者にリクエストを送信できること、保護者が普段使い慣れた場所で意思決定できること、そして最終的にその結果を app に返却できること。これを自前で実装しようとすると、アカウント体系、プッシュ通知、保護者向け UI のすべてを構築する必要があり、技術的なコストは膨大です。

iOS、iPadOS、macOS 26 では、Apple がこの仕組みを Messages に統合しました。新しい PermissionKit フレームワークは Family Sharing をベースにしており、子どもが app 内のボタンをタップすると、システムが Messages 上であらかじめ内容が記入されたメッセージを開いてくれます。宛先はファミリーグループ内の保護者または監督者です。保護者は Messages のバブル内で直接 decline することも、開いて完全なコンテキストを確認したうえで approve することもできます。決定結果は子どもに通知されると同時に、バックグラウンドで app にも届けられます(00:30)。

前提条件は 2 つあります。ユーザーが Family Sharing グループに属していること、そして保護者が子どもに対して Communication Limits を有効にしていることです。いずれかが満たされない場合、API はデフォルトのレスポンスを返します(02:22)。現在のユーザーが子どもかどうかの判定には、自前のアカウントシステムを使うこともできますし、同年に追加された Declared Age Range API を使うこともできます(01:43)。

詳細

最初のステップは、子どもアカウント向けに UI をカスタマイズすることです。未知の差出人に対しては、メッセージのプレビュー、アバター、その他子どもにふさわしくない可能性のあるコンテンツをデフォルトで非表示にします。handle が既知かどうかを判定するには、CommunicationLimits.current.knownHandles(in:) を呼び出します(04:03)。

import PermissionKit

let knownHandles = await CommunicationLimits.current.knownHandles(in: conversation.participants)

if knownHandles.isSuperset(of: conversation.participants) {
    // Show content
} else {
    // Hide content
}

ポイント:

  • CommunicationLimits.current はシングルトンの入り口で、プロセスをまたぐ権限クエリはすべてここから取得します。
  • knownHandles(in:) は async メソッドで、システムが最適化されたバッチクエリを 1 度行い、入力集合のうち既知と判定されたサブセットを返します。
  • isSuperset(of:) は「会話の参加者全員が既知の handle であるか」を判定するために使います。1 人でも未知が含まれていれば非表示分岐に進みます。
  • この API は自前のデータベースを管理してくれるわけではなく、ドキュメントでも既存の連絡先システムと組み合わせて使うことが推奨されています。

第 2 のステップは、PermissionQuestion の構築です。最小限の情報としては handle だけで構いません(05:15)。

import PermissionKit

var question = PermissionQuestion(handles: [
    CommunicationHandle(value: "dragonslayer42", kind: .custom),
    CommunicationHandle(value: "progamer67", kind: .custom)
])

ポイント:

  • CommunicationHandle.value には電話番号、メールアドレス、あるいはカスタムのユーザー名を指定できます。kind: .custom はユーザー名のような非システム識別子であることを表します。
  • handle のみを渡した場合、保護者が目にするのは ID だけなので、判断材料が少なくなります。

より推奨されるのは、メタデータを充実させて、保護者に名前とアバターを見せる方法です(05:38)。

import PermissionKit

let people = [
    PersonInformation(
        handle: CommunicationHandle(value: "dragonslayer42", kind: .custom),
        nameComponents: nameComponents,
        avatarImage: profilePic
    ),
    PersonInformation(
        handle: CommunicationHandle(value: "progamer67", kind: .custom)
    )
]

var topic = CommunicationTopic(personInformation: people)
topic.actions = [.message]

var question = PermissionQuestion(communicationTopic: topic)

ポイント:

  • PersonInformation は handle、nameComponentsavatarImage をひとまとめにします。メタデータが充実しているほど、保護者の意思決定の精度が上がります。
  • CommunicationTopic は question のキャリアであり、actions はシステムが保護者側でどのような表現を使うか(.message.call.video など)を決定します。
  • メタデータはシステムが審査画面で保護者に提示します。当然ながら、これらの情報を正当に保有していることが前提です(05:29)。

第 3 のステップは、question をシステムに渡すことです。SwiftUI では CommunicationLimitsButton を使います(06:25)。

import PermissionKit
import SwiftUI

struct ContentView: View {
    let question: PermissionQuestion<CommunicationTopic>

    var body: some View {
        // ...
        CommunicationLimitsButton(question: question) {
            Label("Ask Permission", systemImage: "paperplane")
        }
    }
}

ポイント:

  • CommunicationLimitsButton は PermissionKit が提供する SwiftUI コントロールで、1 つの question にバインドします。
  • 子どもがボタンをタップすると、システムが alert を表示し、子どもに確認を求めるか、対面で Screen Time のパスコードによる承認を選ばせます。
  • Ask が確認されると、システムはあらかじめ内容が記入された Messages の Compose ウィンドウを開きます。宛先は Family Sharing グループ内の保護者です。

UIKit ではシングルトン経由になります(06:43)。

import PermissionKit
import UIKit

try await CommunicationLimits.current.ask(question, in: viewController)

ポイント:

  • ask(_:in:) は async throws メソッドで、UIViewController を渡してシステムにどこから present するかを判断させます。
  • AppKit における等価な書き方は try await CommunicationLimits.current.ask(question, in: window) で、NSWindow を渡します(06:54)。

第 4 のステップは、保護者からの返答の処理です。保護者が Messages 上で approve または decline をタップすると、子どもの端末はバックグラウンドで起動され、app にコールバックが届きます。監視には AsyncSequence を使います(07:19)。

import PermissionKit
import SwiftUI

struct ChatsView: View {
    @State var isShowingResponseAlert = false

    var body: some View {
        List {
           // ...
        }
        .task {
            let updates = CommunicationLimits.current.updates
            for await update in updates {
                // Received a response!
                self.isShowingResponseAlert = true
            }
        }
    }
}

ポイント:

  • CommunicationLimits.current.updates は AsyncSequence で、保護者の審査結果を購読します。
  • for await.task 修飾子の中で動作し、View が表示されたときに開始され、消えるときに自動的にキャンセルされます。
  • App は background launch によって起動されるため、できるだけ速やかに update を読み取り、永続化や UI の更新を行う必要があります。
  • 決定はシステムから子どもにも通知されるため、自前で再度ローカル通知を送る必要はありません(09:30)。

重要ポイント

  • 何をするか: 全年齢を対象とする app に「保護者へ通信申請を送る」ショートカットを追加する

    • なぜ価値があるか: 保護者の審査システムを自前で構築するコストは非常に高い一方、PermissionKit はチャネル、テンプレート、プッシュ通知、レシートのすべてを Messages に統合してくれます。開発工数はサブシステム 1 つ分から、わずか数十行のコードへと圧縮されます。
    • どう始めるか: まず Declared Age Range API か自前のアカウントで子どもユーザーを識別します。次に、見知らぬ相手からのメッセージを受け取った際に CommunicationLimitsButton を差し込み、現在の handle を PermissionQuestion にラップします。
  • 何をするか: メタデータをコンバージョン率の指標として磨き込む

    • なぜ価値があるか: 保護者側に表示される名前、アバター、行為の種類は、approve 率に直接影響します。メタデータが欠けていると、保護者は decline を選びがちになります。
    • どう始めるか: PersonInformation を構築するときには nameComponentsavatarImage をできるだけ埋めます。ユーザー名しか取れない場合でも、せめて CommunicationTopic.actions に message、call、video のいずれかを正確に宣言し、システムに正しい動詞を使わせましょう。
  • 何をするか: 審査結果を自前のサーバーに同期し、クロスプラットフォームでの一貫性を保つ

    • なぜ価値があるか: PermissionKit は Apple のエコシステムしかカバーしません。Web 版や Android 版の子どもユーザーにも、同じ保護ロジックが必要です。
    • どう始めるか: CommunicationLimits.current.updatesfor await の中で、各 update を自前のバックエンドに送信し、権限のマスターソースとします。他のクライアントはバックエンドの設定を読みに行く形にし、それぞれが個別の状態を持たないようにします。
  • 何をするか: 「ファミリーグループが存在しない」「Communication Limits が未有効」の 2 つの状態に対するフォールバックを用意する

    • なぜ価値があるか: API はデフォルトのレスポンスを返します。UI で何も説明しないと、子どもはボタンが壊れていると思い、保護者は app が暗黙的に許可していると誤解します。
    • どう始めるか: ask する前に「保護者がまず Settings で Communication Limits を有効にする必要があります」と表示します。デフォルトレスポンスの分岐では明確な文言を表示し、対面で Screen Time のパスコードによる承認を行う入り口を用意します。

関連セッション

コメント

GitHub Issues · utterances