permissionkit について
permissionkitは、PermissionKitを使った保護者承認フローの開発を支援するスキルです。子どもの通信制限に対する例外申請を扱い、質問の作成、申請、非同期応答を解説します。この体験はiMessage経由に限られます。
主な機能
- 01システムに登録された通信先の確認
- 02通信許可の質問作成
- 03AskCenterまたはPermissionButtonでの申請開始
- 04非同期の応答受信と処理
- 05保留、キャンセル、エラー状態の管理
活用シーン
- 01制限対象への連絡を子どもが申請
- 02通信例外の保護者承認フロー構築
- 03重大なアプリ更新への保護者承認申請
- 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 経由でのみ利用できます。親または保護者の承認フローに使用し、一般的なアプリ内の連絡先へのアクセス許可、モデレーション、チャットの安全性を扱うフレームワークとしては使用しないでください。
目次
- 利用条件とセットアップ
- 基本概念
- コミュニケーション制限の確認
- 許可の質問を作成する
- AskCenter で許可を求める
- PermissionButton と SwiftUI を統合する
- 応答の処理
- 重要なアプリのアップデートに関するトピック
- よくある間違い
- レビュー用チェックリスト
- 参考資料
利用条件とセットアップ
PermissionKit をインポートします。PermissionKit のエンタイトルメントキーを推測で追加しないでください。署名要件を追加する前に、Apple の最新ドキュメントと Xcode の機能を確認してください。
import PermissionKit
以下のバージョン一覧を一元的に参照し、現在の SDK に照らして確認してください。
| ティア | API | iOS/iPadOS/Mac Catalyst/macOS/visionOS |
|---|---|---|
| コア | トピック、ハンドル、質問、応答、選択肢、CommunicationLimits | 26.0 以降 |
| エラー | AskError | 26.1 以降 |
| 表示 | AskCenter、質問/応答シーケンス、PermissionButton、重要なアップデートに関するトピック | 26.2 以降 |
基本概念
PermissionKit は、次のようなフローを管理します。
- 子どもがアプリ内でコミュニケーション制限に遭遇します
- アプリがリクエストの内容を示す
PermissionQuestionを作成します - システムが子どもに質問を提示し、親に送信できるようにします
- 親がリクエストを確認し、承認または拒否します
- アプリが親の判断を含む
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 が更新される
- エラー状態で、ユーザーに明確な案内を提供している