WWDC Quick Look đź’“ By SwiftGGTeam
Enhance child safety with PermissionKit

Enhance child safety with PermissionKit

Watch original video

Highlight

PermissionKit solves a narrow problem: when a child wants to start a conversation with a stranger inside an app, how does the app safely get the parent’s permission?

Core content

A child receives a first message from a stranger inside an app. Every social, chat, and game app that targets all ages runs into this. The developer needs a chain: let the child ask the parent, let the parent decide where they already are, and ship the result back to the app. Building this yourself means an account system, push, and a parent-side UI. The cost is huge.

On iOS, iPadOS, and macOS 26, Apple wires this into Messages. The new PermissionKit framework builds on Family Sharing. The child taps a button in your app, and the system opens a pre-filled message in Messages addressed to a parent or guardian in the family group. The parent can decline right from the Messages bubble, or tap in to read the full context and approve. The decision goes back to the child and is delivered to your app in the background (00:30).

Two preconditions: the user must be in a Family Sharing group, and the parent must have turned on Communication Limits for the child. If either is missing, the API returns a default response (02:22). To tell whether the current user is a child, use your own account system, or use the new Declared Age Range API shipped the same year (01:43).

Detailed content

Step one: tailor the UI for the child account. For unknown senders, hide message previews, avatars, and other content that may not be appropriate for a child by default. To check whether a handle is known, call CommunicationLimits.current.knownHandles(in:) (04:03):

import PermissionKit

let knownHandles = await CommunicationLimits.current.knownHandles(in: conversation.participants)

if knownHandles.isSuperset(of: conversation.participants) {
    // Show content
} else {
    // Hide content
}

Key points:

  • CommunicationLimits.current is the singleton entry. Every cross-process permission query goes through it.
  • knownHandles(in:) is async. The system runs an optimized batch query and returns the subset of the input that it recognizes.
  • isSuperset(of:) checks whether every participant in the conversation is a known handle. One unknown participant flips you to the hide branch.
  • This API does not manage your own database. The docs suggest merging it with your existing contacts system.

Step two: build a PermissionQuestion. The minimum is a handle (05:15):

import PermissionKit

var question = PermissionQuestion(handles: [
    CommunicationHandle(value: "dragonslayer42", kind: .custom),
    CommunicationHandle(value: "progamer67", kind: .custom)
])

Key points:

  • CommunicationHandle.value can be a phone number, an email, or a custom username. kind: .custom marks a non-system identifier such as a username.
  • A handle alone gives the parent only an ID. There is little to decide on.

The better path is to fill in metadata so the parent sees a name and an avatar (05:38):

import PermissionKit

let people = [
    PersonInformation(
        handle: CommunicationHandle(value: "dragonslayer42", kind: .custom),
        nameComponents: nameComponents,
        avatarImage: profilePic
    ),
    PersonInformation(
        handle: CommunicationHandle(value: "progamer67", kind: .custom)
    )
]

var topic = CommunicationTopic(personInformation: people)
topic.actions = [.message]

var question = PermissionQuestion(communicationTopic: topic)

Key points:

  • PersonInformation bundles the handle, nameComponents, and avatarImage. The more metadata, the better the parent’s decision.
  • CommunicationTopic carries the question. actions decides the wording on the parent’s side (.message, .call, .video, and so on).
  • The system shows the metadata to the parent in the approval UI, on the condition that you legally hold this information (05:29).

Step three: hand the question to the system. In SwiftUI, use CommunicationLimitsButton (06:25):

import PermissionKit
import SwiftUI

struct ContentView: View {
    let question: PermissionQuestion<CommunicationTopic>

    var body: some View {
        // ...
        CommunicationLimitsButton(question: question) {
            Label("Ask Permission", systemImage: "paperplane")
        }
    }
}

Key points:

  • CommunicationLimitsButton is the SwiftUI control PermissionKit provides. It binds to a question.
  • When the child taps it, the system shows an alert. The child confirms, or chooses in-person authorization with the Screen Time passcode.
  • After the child confirms Ask, the system opens a pre-filled Messages compose window addressed to a parent in the Family Sharing group.

UIKit goes through the singleton (06:43):

import PermissionKit
import UIKit

try await CommunicationLimits.current.ask(question, in: viewController)

Key points:

  • ask(_:in:) is an async throws method. Pass a UIViewController and let the system pick where to present from.
  • The AppKit equivalent is try await CommunicationLimits.current.ask(question, in: window), taking an NSWindow (06:54).

Step four: handle the parent’s reply. The parent taps approve or decline in Messages. The child’s device wakes in the background, and your app receives a callback. Listen with an AsyncSequence (07:19):

import PermissionKit
import SwiftUI

struct ChatsView: View {
    @State var isShowingResponseAlert = false

    var body: some View {
        List {
           // ...
        }
        .task {
            let updates = CommunicationLimits.current.updates
            for await update in updates {
                // Received a response!
                self.isShowingResponseAlert = true
            }
        }
    }
}

Key points:

  • CommunicationLimits.current.updates is an AsyncSequence. It streams the parent’s approval results.
  • for await runs inside the .task modifier. It starts when the view appears and cancels when the view disappears.
  • The app is woken by a background launch. Read the update fast and write it to disk or refresh the UI.
  • The decision is also delivered to the child by notification, so you do not need to fire your own local notification (09:30).

Key takeaways

  • What to do: add a “ask a parent for permission” shortcut in apps that target all ages

    • Why it is worth doing: building your own parental approval system costs a lot. PermissionKit hands you the channel, the template, the push, and the receipt through Messages. The work drops from a full subsystem to a few dozen lines.
    • How to start: identify child users with the Declared Age Range API or your own account system. Then drop a CommunicationLimitsButton into the unknown-sender flow and wrap the current handle in a PermissionQuestion.
  • What to do: treat metadata as a conversion-rate metric

    • Why it is worth doing: the name, avatar, and action type the parent sees drive the approve rate. With metadata missing, parents tend to decline outright.
    • How to start: fill in nameComponents and avatarImage when building PersonInformation. If you only have a username, at least set CommunicationTopic.actions correctly to message, call, or video so the system uses the right verb.
  • What to do: sync approval results to your own server for cross-platform consistency

    • Why it is worth doing: PermissionKit only covers the Apple ecosystem. Child users on the web and Android need the same protection.
    • How to start: in the for await over CommunicationLimits.current.updates, post each update to your backend as the source of truth. Other clients read the toggle from the backend instead of each maintaining its own copy.
  • What to do: degrade gracefully when the family group is missing or Communication Limits is off

    • Why it is worth doing: the API returns a default response. If the UI does not explain it, the child thinks the button is broken, and the parent thinks the app is silently allowing things.
    • How to start: before calling ask, prompt that “the parent needs to enable Communication Limits in Settings first.” Show clear copy on the default-response branch, and offer in-person authorization with the Screen Time passcode.

Comments

GitHub Issues · utterances