关于 permissionkit
permissionkit 是指导开发者使用 PermissionKit 构建家长或监护人审批流程的技能,适用于儿童通信限制的例外申请。内容包括提问、请求发送及异步处理审批结果,并明确该流程仅通过 iMessage 提供。
主要功能
- 01检查系统已知的通信对象
- 02创建通信权限问题
- 03通过 AskCenter 或 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:) 还要求调用应用具有非空的 bundle 标识符。修正后的代码应在调用该方法前检查 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)
将 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 跟踪待处理问题,并为 UI 提供重试或过期处理方式,因为孩子可以取消 iMessage 发送流程而不产生响应。
将已知句柄检查与响应处理结合时,沿用 knownHandles(in:) 中的 bundle 标识符保护逻辑。
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
重大 App 更新主题
请求对需要家长批准的重大 App 更新进行授权。你的 App 应根据适用法规确定哪些情况属于重大更新,并应咨询具备相应资质的法律顾问,以解读合规要求。 使用简洁易懂的描述,明确说明家长要批准的具体变更。
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 合并为一条消息 | 区分限制未启用、联系人同步、问题无效、不可用、系统和未知情况。 |
| 问题未包含句柄或人员信息 | 在展示前,至少验证一个有意义的通信目标。 |
| Ask 调用即发即忘 | 观察响应和待处理状态,同时允许孩子取消或放弃。 |
使用已弃用的 CommunicationLimitsButton | 使用 PermissionButton。 |
检查清单
- 选择 PermissionKit 前,已了解仅通过 iMessage 路由的限制
- 对正在使用的每个 API,均应用了集中管理的可用性矩阵
- 使用正确的
Kind(phone、email、custom)创建CommunicationHandle - 已知句柄示例在调用
knownHandles(in:)前,会检查 bundle 标识符非 nil 且非空 - 人员信息包含姓名组成部分,以便清晰呈现权限提示
- 通信操作与 App 的实际通信能力相符
- 响应处理会在主线程执行器上更新 UI
- 错误状态为用户提供清晰的指导