permissionkit

permissionkit

热门

使用 PermissionKit 为儿童创建通信安全体验,请求家长许可。适用于涉及儿童与联系人通信的应用,需要检查通信限制、请求家长/监护人批准或处理未成年人的权限响应。

936Star
47Fork
更新于 2026/7/15
SKILL.md
readonly只读
name
permissionkit
description

使用 PermissionKit 为儿童创建通信安全体验,请求家长许可。适用于涉及儿童与联系人通信的应用,需要检查通信限制、请求家长/监护人批准或处理未成年人的权限响应。

PermissionKit

向家长或监护人请求许可,以修改儿童的通信规则。PermissionKit 创建通信安全体验,让儿童可以请求对家长设置的通信限制进行例外处理。

PermissionKit 通信体验仅通过 iMessage 提供。请将其用于家长/监护人批准流程,而不是作为通用的应用内联系人权限、审核或聊天安全框架。

目录

可用性和设置

导入 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 管理以下流程:

  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 标识符。修正后的代码应在调用之前保护 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
  • [ ] 错误状态为用户提供清晰的指导

参考资料