mcpField
P
permissionkit

permissionkit

github.com/dpearson2699開発1.2k3.6k 回インストール13 週間前に更新
リポジトリ

permissionkit について

permissionkitは、PermissionKitを使った保護者承認フローの開発を支援するスキルです。子どもの通信制限に対する例外申請を扱い、質問の作成、申請、非同期応答を解説します。この体験はiMessage経由に限られます。

主な機能

  1. 01システムに登録された通信先の確認
  2. 02通信許可の質問作成
  3. 03AskCenterまたはPermissionButtonでの申請開始
  4. 04非同期の応答受信と処理
  5. 05保留、キャンセル、エラー状態の管理

活用シーン

  1. 01制限対象への連絡を子どもが申請
  2. 02通信例外の保護者承認フロー構築
  3. 03重大なアプリ更新への保護者承認申請
  4. 04保留中や期限切れの申請追跡

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill permissionkit

インストール後、Claude Code で/permissionkitを入力して読み込まれたか確認します。

同梱ファイルevals/evals.jsonreferences/permissionkit-patterns.md

詳細

PermissionKit

子どものコミュニケーションルールを変更するため、親または保護者に許可を求めます。PermissionKit は、子どもが親の設定したコミュニケーション制限の例外を求められる、安全なコミュニケーション体験を作成します。

PermissionKit のコミュニケーション体験は、iMessage 経由でのみ利用できます。親または保護者の承認フローに使用し、一般的なアプリ内の連絡先へのアクセス許可、モデレーション、チャットの安全性を扱うフレームワークとしては使用しないでください。

目次

利用条件とセットアップ

PermissionKit をインポートします。PermissionKit のエンタイトルメントキーを推測で追加しないでください。署名要件を追加する前に、Apple の最新ドキュメントと Xcode の機能を確認してください。

import PermissionKit

以下のバージョン一覧を一元的に参照し、現在の SDK に照らして確認してください。

ティアAPIiOS/iPadOS/Mac Catalyst/macOS/visionOS
コアトピック、ハンドル、質問、応答、選択肢、CommunicationLimits26.0 以降
エラーAskError26.1 以降
表示AskCenter、質問/応答シーケンス、PermissionButton、重要なアップデートに関するトピック26.2 以降

基本概念

PermissionKit は、次のようなフローを管理します。

  1. 子どもがアプリ内でコミュニケーション制限に遭遇します
  2. アプリがリクエストの内容を示す PermissionQuestion を作成します
  3. システムが子どもに質問を提示し、親に送信できるようにします
  4. 親がリクエストを確認し、承認または拒否します
  5. アプリが親の判断を含む PermissionResponse を受け取ります
主な型
型役割
AskCenter許可リクエストと応答を管理するシングルトン
PermissionQuestion求めている許可の内容を示します
PermissionResponse親の判断(承認または拒否)
PermissionChoice具体的な回答(承認/拒否)
PermissionButton許可フローを開始する SwiftUI ボタン
CommunicationTopicコミュニケーション関連の許可リクエスト用トピック
CommunicationHandle電話番号、メールアドレス、またはカスタム識別子
CommunicationLimitsどのコミュニケーションハンドルがシステムに登録されているかを確認します
SignificantAppUpdateTopic重要なアプリのアップデートに関する許可リクエスト用トピック

コミュニケーション制限の確認

CommunicationLimits.current を使用して、システムがアプリのコミュニケーションハンドルをすでに認識しているか確認します。これは「コミュニケーション制限が有効か」を調べるためのものではありません。制限が有効でない場合、AskCenter.shared.ask(_:in:) は AskError.communicationLimitsNotEnabled をスローします。許可を求める際は、このケースを処理してください。

knownHandles(in:) を呼び出すには、呼び出し元アプリのバンドル識別子が nil ではなく、空でもないことも必要です。修正後のコードでは、呼び出す前に Bundle.main.bundleIdentifier を確認してください。

import PermissionKit

func needsPermissionPrompt(for handle: CommunicationHandle) async -> Bool {
    let limits = CommunicationLimits.current
    let isKnown = await limits.isKnownHandle(handle)
    return !isKnown
}

// Check multiple handles at once.
func filterKnownHandles(_ handles: Set<CommunicationHandle>) async -> Set<CommunicationHandle> {
    guard Bundle.main.bundleIdentifier?.isEmpty == false else { return [] }

    let limits = CommunicationLimits.current
    return await limits.knownHandles(in: handles)
}
コミュニケーションハンドルの作成
let phoneHandle = CommunicationHandle(
    value: "+1234567890",
    kind: .phoneNumber
)

let emailHandle = CommunicationHandle(
    value: "[email protected]",
    kind: .emailAddress
)

let customHandle = CommunicationHandle(
    value: "user123",
    kind: .custom
)

許可の質問を作成する

連絡先情報とコミュニケーションアクションの種類を指定して、PermissionQuestion を作成します。

// Question for a single contact
let handle = CommunicationHandle(value: "+1234567890", kind: .phoneNumber)
let question = PermissionQuestion<CommunicationTopic>(handle: handle)

// Question for multiple contacts
let handles = [
    CommunicationHandle(value: "+1234567890", kind: .phoneNumber),
    CommunicationHandle(value: "[email protected]", kind: .emailAddress)
]
let multiQuestion = PermissionQuestion<CommunicationTopic>(handles: handles)
Person 情報を使った CommunicationTopic の使用

表示名とアバターを指定して、より充実した許可プロンプトを表示します。

let personInfo = CommunicationTopic.PersonInformation(
    handle: CommunicationHandle(value: "+1234567890", kind: .phoneNumber),
    nameComponents: {
        var name = PersonNameComponents()
        name.givenName = "Alex"
        name.familyName = "Smith"
        return name
    }(),
    avatarImage: nil
)

let topic = CommunicationTopic(
    personInformation: [personInfo],
    actions: [.message, .audioCall]
)

let question = PermissionQuestion<CommunicationTopic>(communicationTopic: topic)
コミュニケーションアクション
アクション説明
.messageテキストメッセージ
.audioCall音声通話
.videoCallビデオ通話
.call一般的な通話
.chatチャットでのコミュニケーション
.followユーザーをフォローする
.beFollowedフォローを許可する
.friend友達リクエスト
.connectつながりのリクエスト
.communicate一般的なコミュニケーション

AskCenter で許可をリクエストする

AskCenter.shared を使用して、お子さまから親または保護者へ許可に関する質問を送信します。非同期の ask 呼び出しで送信フローが開始され、親の判断は後から responses(for:) を通じて届きます。お子さまが送信フローをキャンセルした場合、その質問に対する PermissionResponse はシステムから配信されません。

import PermissionKit

func requestPermission(
    for question: PermissionQuestion<CommunicationTopic>,
    in viewController: UIViewController
) async {
    do {
        try await AskCenter.shared.ask(question, in: viewController)
        // Question send flow was started; wait for responses(for:) separately.
    } catch let error as AskError {
        switch error {
        case .communicationLimitsNotEnabled:
            // Communication limits not active -- continue with normal app flow.
            break
        case .contactSyncNotSetup:
            // Contact sync not configured
            break
        case .invalidQuestion:
            // Question is malformed
            break
        case .notAvailable:
            // PermissionKit not available on this device
            break
        case .systemError(let underlying):
            print("System error: \(underlying)")
        case .unknown:
            break
        @unknown default:
            break
        }
    }
}

PermissionButton による SwiftUI との統合

PermissionButton は、タップすると許可フローを開始する SwiftUI ビューです。AskCenter と同じ応答モデルを使用します。応答を監視し、タップするたびに親の判断が返されると想定せず、保留中またはキャンセル済みの状態をモデル化してください。

import SwiftUI
import PermissionKit

struct ContactPermissionView: View {
    let handle = CommunicationHandle(value: "+1234567890", kind: .phoneNumber)

    var body: some View {
        let question = PermissionQuestion<CommunicationTopic>(handle: handle)

        PermissionButton(question: question) {
            Label("Ask to Message", systemImage: "message")
        }
    }
}

より高度な SwiftUI フロー、カスタムトピック、長期間存続するマネージャーについては、 references/permissionkit-patterns.md をお読みください。

応答の処理

非同期で許可応答を監視します。保留中の質問は question.id で追跡し、お子さまが iMessage の送信フローをキャンセルして応答が生成されない場合に備えて、UI に再試行または有効期限切れの経路を用意してください。 既知のハンドルの確認と応答処理を組み合わせる場合は、knownHandles(in:) に対するバンドル識別子のガードを引き継いでください。

enum PermissionRequestState {
    case pending, approved, denied, expired
}

var requestStates: [UUID: PermissionRequestState] = [:]

func expireIfStillPending(_ id: UUID) {
    guard requestStates[id] == .pending else { return }
    requestStates[id] = .expired
    // Re-enable asking or show retry/canceled UI.
}

func observeResponses() async {
    let responses = AskCenter.shared.responses(for: CommunicationTopic.self)

    for await response in responses {
        let choice = response.choice
        let question = response.question

        switch choice.answer {
        case .approval:
            // Parent approved -- enable communication
            requestStates[question.id] = .approved
            print("Approved for topic: \(question.topic)")
        case .denial:
            // Parent denied -- keep restriction
            requestStates[question.id] = .denied
            print("Denied")
        @unknown default:
            break
        }
    }
}
PermissionChoice のプロパティ
let choice: PermissionChoice = response.choice
print("Answer: \(choice.answer)")  // .approval or .denial
print("Choice ID: \(choice.id)")
print("Title: \(choice.title)")

// Convenience statics
let approved = PermissionChoice.approve
let declined = PermissionChoice.decline

重要なアプリのアップデートに関するトピック

保護者の承認が必要となる重要なアプリのアップデートについて、許可をリクエストします。何を重要とみなすかは、適用される規制に基づいてアプリが判断し、コンプライアンスの解釈については、資格を持つ法律専門家に相談してください。保護者が承認する具体的な変更内容を明示した、簡潔で分かりやすい説明を使用してください。

let updateTopic = SignificantAppUpdateTopic(
    description: "This update adds multiplayer chat features"
)

let question = PermissionQuestion<SignificantAppUpdateTopic>(
    significantAppUpdateTopic: updateTopic
)

// Present the question
try await AskCenter.shared.ask(question, in: viewController)
requestStates[question.id] = .pending
scheduleExpiration(for: question.id)

// Listen for responses
for await response in AskCenter.shared.responses(for: SignificantAppUpdateTopic.self) {
    switch response.choice.answer {
    case .approval:
        // Proceed with update
        requestStates[response.question.id] = .approved
    case .denial:
        // Skip update
        requestStates[response.question.id] = .denied
    @unknown default:
        break
    }
}

// If no response arrives before your pending window expires, keep the update
// blocked or offer a retry. Child cancellation produces no denial response.

よくある間違い

間違い修正方法
既知のハンドルを検索できたことを、制限が有効である証拠として扱うask 操作で .communicationLimitsNotEnabled が返された場合は、未設定時の通常の経路として処理します。
AskError を単一のメッセージにまとめてしまう制限が無効、連絡先の同期、無効な質問、利用不可、システム、その他不明なケースを区別します。
質問にハンドルまたは人物情報がない表示する前に、意味のある連絡先が少なくとも 1 つあることを検証します。
Ask を結果を待たずに実行するお子さまによるキャンセルや中断を許容しつつ、応答と保留中の状態を監視します。
非推奨の CommunicationLimitsButton を使用するPermissionButton を使用します。

レビューチェックリスト

  • PermissionKit を選択する前に、iMessage のみを対象とするルーティングについて理解している
  • 使用するすべての API に、一元化された利用可能性マトリックスを適用している
  • CommunicationHandle を適切な Kind(電話、メール、カスタム)で作成している
  • 既知のハンドルの例では、knownHandles(in:) の前に、バンドル識別子が nil ではなく空でもないことを確認している
  • 分かりやすい許可プロンプトになるよう、人物情報に名前の各要素を含めている
  • 連絡手段のアクションが、アプリの実際の連絡機能と一致している
  • 応答処理によって、メインアクター上で UI が更新される
  • エラー状態で、ユーザーに明確な案内を提供している

参考資料

コメント