使用 PermissionKit 为儿童创建通信安全体验,请求家长许可。适用于涉及儿童与联系人通信的应用,需要检查通信限制、请求家长/监护人批准或处理未成年人的权限响应。
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 标识符。修正后的代码应在调用之前保护 Bundle.main.bundleIdentifier。
import PermissionKit
func needsPermissionPrompt(for handle: CommunicationHandle) async -> Bool {
let limits = CommunicationLimits.current
let isKnown = await limits.isKnownHandle(handle)
return !isKnown
}
// 同时检查多个句柄。
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: "friend@example.com",
kind: .emailAddress
)
let customHandle = CommunicationHandle(
value: "user123",
kind: .custom
)
创建权限问题
使用联系信息和通信操作类型构建 PermissionQuestion。
// 单个联系人的问题
let handle = CommunicationHandle(value: "+1234567890", kind: .phoneNumber)
let question = PermissionQuestion<CommunicationTopic>(handle: handle)
// 多个联系人的问题
let handles = [
CommunicationHandle(value: "+1234567890", kind: .phoneNumber),
CommunicationHandle(value: "friend@example.com", 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)
// 问题发送流程已启动;稍后单独等待 responses(for:)。
} catch let error as AskError {
switch error {
case .communicationLimitsNotEnabled:
// 通信限制未激活——继续正常应用流程。
break
case .contactSyncNotSetup:
// 联系人同步未配置
break
case .invalidQuestion:
// 问题格式错误
break
case .notAvailable:
// 此设备上 PermissionKit 不可用
break
case .systemError(let underlying):
print("系统错误:\(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("请求发送消息", 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
// 重新启用询问或显示重试/取消 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:
// 家长批准——启用通信
requestStates[question.id] = .approved
print("主题已批准:\(question.topic)")
case .denial:
// 家长拒绝——保持限制
requestStates[question.id] = .denied
print("已拒绝")
@unknown default:
break
}
}
}
PermissionChoice 属性
let choice: PermissionChoice = response.choice
print("答案:\(choice.answer)") // .approval 或 .denial
print("选项 ID:\(choice.id)")
print("标题:\(choice.title)")
// 便捷静态属性
let approved = PermissionChoice.approve
let declined = PermissionChoice.decline
重大应用更新主题
为需要家长批准的重大应用更新请求权限。您的应用根据适用法规确定什么算作重大更新,并应咨询合格的法律顾问以进行合规性解释。使用简洁、易懂的描述,说明家长正在批准的具体变更。
let updateTopic = SignificantAppUpdateTopic(
description: "此更新添加了多人聊天功能"
)
let question = PermissionQuestion<SignificantAppUpdateTopic>(
significantAppUpdateTopic: updateTopic
)
// 展示问题
try await AskCenter.shared.ask(question, in: viewController)
requestStates[question.id] = .pending
scheduleExpiration(for: question.id)
// 监听响应
for await response in AskCenter.shared.responses(for: SignificantAppUpdateTopic.self) {
switch response.choice.answer {
case .approval:
// 继续更新
requestStates[response.question.id] = .approved
case .denial:
// 跳过更新
requestStates[response.question.id] = .denied
@unknown default:
break
}
}
// 如果在待处理窗口过期前没有收到响应,则保持更新阻止或提供重试。儿童取消不会产生拒绝响应。
常见错误
| 错误 | 修复 |
|---|---|
| 已知句柄查找被视为限制已启用的证据 | 将询问操作中的 .communicationLimitsNotEnabled 作为正常的未配置路径处理。 |
AskError 被合并为一条消息 |
区分限制禁用、联系人同步、问题无效、不可用、系统和未知情况。 |
| 问题没有句柄或个人信息 | 在展示前验证至少一个有意义的通信目标。 |
| 询问后即忘 | 观察响应和待处理状态,同时允许儿童取消/放弃。 |
使用了已弃用的 CommunicationLimitsButton |
使用 PermissionButton。 |
审核清单
- [ ] 在选择 PermissionKit 之前理解仅限 iMessage 的路由
- [ ] 对使用的每个 API 应用集中可用性矩阵
- [ ] 使用正确的
Kind(电话、电子邮件、自定义)创建CommunicationHandle - [ ] 已知句柄示例在
knownHandles(in:)之前保护非 nil、非空的 bundle 标识符 - [ ] 个人信息包含名称组件以获得清晰的权限提示
- [ ] 通信操作与应用的实际通信能力匹配
- [ ] 响应处理在主 actor 上更新 UI
- [ ] 错误状态为用户提供清晰的指导






