Loading...
Loading...
Create child communication safety experiences using PermissionKit to request parental permission for children. Use when building apps that involve child-to-contact communication, need to check communication limits, request parent/guardian approval, or handle permission responses for minors.
npx skill4agent add dpearson2699/swift-ios-skills permissionkitNote: PermissionKit is new in iOS 26. Method signatures should be verified against the latest Xcode 26 beta SDK.
PermissionKitimport PermissionKitPermissionQuestionPermissionResponse| Type | Role |
|---|---|
| Singleton that manages permission requests and responses |
| Describes the permission being requested |
| The parent's decision (approval or denial) |
| The specific answer (approve/decline) |
| SwiftUI button that triggers the permission flow |
| Topic for communication-related permission requests |
| A phone number, email, or custom identifier |
| Checks whether communication limits apply |
| Topic for significant app update permission requests |
import PermissionKit
func checkCommunicationStatus(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> {
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// 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: "friend@example.com", kind: .emailAddress)
]
let multiQuestion = PermissionQuestion<CommunicationTopic>(handles: handles)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)| Action | Description |
|---|---|
| Text messaging |
| Voice call |
| Video call |
| Generic call |
| Chat communication |
| Follow a user |
| Allow being followed |
| Friend request |
| Connection request |
| Generic communication |
AskCenter.sharedimport PermissionKit
func requestPermission(
for question: PermissionQuestion<CommunicationTopic>,
in viewController: UIViewController
) async {
do {
try await AskCenter.shared.ask(question, in: viewController)
// Question was presented to the child
} catch let error as AskError {
switch error {
case .communicationLimitsNotEnabled:
// Communication limits not active -- no permission needed
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
}
}
}PermissionButtonimport 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")
}
}
}struct CustomPermissionView: View {
var body: some View {
let personInfo = CommunicationTopic.PersonInformation(
handle: CommunicationHandle(value: "user456", kind: .custom),
nameComponents: nil,
avatarImage: nil
)
let topic = CommunicationTopic(
personInformation: [personInfo],
actions: [.follow]
)
let question = PermissionQuestion<CommunicationTopic>(
communicationTopic: topic
)
PermissionButton(question: question) {
Text("Ask to Follow")
}
}
}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
print("Approved for topic: \(question.topic)")
case .denial:
// Parent denied -- keep restriction
print("Denied")
@unknown default:
break
}
}
}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.declinelet 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)
// Listen for responses
for await response in AskCenter.shared.responses(for: SignificantAppUpdateTopic.self) {
switch response.choice.answer {
case .approval:
// Proceed with update
break
case .denial:
// Skip update
break
@unknown default:
break
}
}ask.communicationLimitsNotEnabled// WRONG: Assuming limits are always active
try await AskCenter.shared.ask(question, in: viewController)
// CORRECT: Handle the case where limits are not enabled
do {
try await AskCenter.shared.ask(question, in: viewController)
} catch AskError.communicationLimitsNotEnabled {
// Communication limits not active -- allow communication directly
allowCommunication()
} catch {
handleError(error)
}// WRONG: Catch-all with no user feedback
do {
try await AskCenter.shared.ask(question, in: viewController)
} catch {
print(error)
}
// CORRECT: Handle each case
do {
try await AskCenter.shared.ask(question, in: viewController)
} catch let error as AskError {
switch error {
case .communicationLimitsNotEnabled:
allowCommunication()
case .contactSyncNotSetup:
showContactSyncPrompt()
case .invalidQuestion:
showInvalidQuestionAlert()
case .notAvailable:
showUnavailableMessage()
case .systemError(let underlying):
showSystemError(underlying)
case .unknown:
showGenericError()
@unknown default:
break
}
}// WRONG: Empty handles array
let question = PermissionQuestion<CommunicationTopic>(handles: []) // Invalid
// CORRECT: Provide at least one handle
let handle = CommunicationHandle(value: "+1234567890", kind: .phoneNumber)
let question = PermissionQuestion<CommunicationTopic>(handle: handle)// WRONG: Fire and forget
try await AskCenter.shared.ask(question, in: viewController)
// CORRECT: Observe responses
Task {
for await response in AskCenter.shared.responses(for: CommunicationTopic.self) {
handleResponse(response)
}
}
try await AskCenter.shared.ask(question, in: viewController)PermissionButtonCommunicationLimitsButton// WRONG: Deprecated
CommunicationLimitsButton(question: question) {
Text("Ask Permission")
}
// CORRECT: Use PermissionButton
PermissionButton(question: question) {
Text("Ask Permission")
}AskError.communicationLimitsNotEnabledAskErrorCommunicationHandleKindPermissionQuestionAskCenter.shared.responses(for:)PermissionButtonCommunicationLimitsButtonreferences/permissionkit-patterns.md