Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Overflow reference for the permissionkit skill. Contains advanced patterns
that exceed the main skill file's scope.
Complete UIKit view controller with permission request and response handling.
import UIKit
import PermissionKit
class ContactViewController: UIViewController {
private var responseTask: Task<Void, Never>?
override func viewDidLoad() {
super.viewDidLoad()
startObservingResponses()
}
deinit {
responseTask?.cancel()
}
func requestPermissionToMessage(_ contact: Contact) {
let personInfo = CommunicationTopic.PersonInformation(
handle: CommunicationHandle(
value: contact.phoneNumber,
kind: .phoneNumber
),
nameComponents: contact.nameComponents,
avatarImage: contact.avatarCGImage
)
let topic = CommunicationTopic(
personInformation: [personInfo],
actions: [.message]
)
let question = PermissionQuestion<CommunicationTopic>(
communicationTopic: topic
)
Task {
do {
try await AskCenter.shared.ask(question, in: self)
showPendingState(for: contact)
} catch AskError.communicationLimitsNotEnabled {
enableMessaging(for: contact)
} catch AskError.notAvailable {
showFeatureUnavailable()
} catch {
showError(error)
}
}
}
private func startObservingResponses() {
responseTask = Task { [weak self] in
let responses = AskCenter.shared.responses(
for: CommunicationTopic.self
)
for await response in responses {
await MainActor.run {
self?.handleResponse(response)
}
}
}
}
@MainActor
private func handleResponse(_ response: PermissionResponse<CommunicationTopic>) {
switch response.choice.answer {
case .approval:
let handles = response.question.topic.personInformation
.map(\.handle)
for handle in handles {
enableCommunication(for: handle)
}
case .denial:
let handles = response.question.topic.personInformation
.map(\.handle)
for handle in handles {
showDeniedState(for: handle)
}
@unknown default:
break
}
}
private func showPendingState(for contact: Contact) { }
private func enableMessaging(for contact: Contact) { }
private func enableCommunication(for handle: CommunicationHandle) { }
private func showDeniedState(for handle: CommunicationHandle) { }
private func showFeatureUnavailable() { }
private func showError(_ error: Error) { }
}Centralize response observation for apps with multiple permission flows.
import PermissionKit
@Observable
@MainActor
final class PermissionManager {
static let shared = PermissionManager()
var approvedHandles: Set<String> = []
var deniedHandles: Set<String> = []
var pendingQuestionIDs: Set<UUID> = []
private var observerTask: Task<Void, Never>?
private init() {
startObserving()
}
deinit {
observerTask?.cancel()
}
func askPermission(
for handles: [CommunicationHandle],
actions: Set<CommunicationTopic.Action>,
in viewController: UIViewController
) async throws {
let personInfo = handles.map { handle in
CommunicationTopic.PersonInformation(
handle: handle,
nameComponents: nil,
avatarImage: nil
)
}
let topic = CommunicationTopic(
personInformation: personInfo,
actions: actions
)
let question = PermissionQuestion<CommunicationTopic>(
communicationTopic: topic
)
try await AskCenter.shared.ask(question, in: viewController)
pendingQuestionIDs.insert(question.id)
}
func isApproved(_ handleValue: String) -> Bool {
approvedHandles.contains(handleValue)
}
func isDenied(_ handleValue: String) -> Bool {
deniedHandles.contains(handleValue)
}
private func startObserving() {
observerTask = Task { [weak self] in
let responses = AskCenter.shared.responses(
for: CommunicationTopic.self
)
for await response in responses {
await MainActor.run {
self?.processResponse(response)
}
}
}
}
private func processResponse(
_ response: PermissionResponse<CommunicationTopic>
) {
pendingQuestionIDs.remove(response.question.id)
let handleValues = response.question.topic.personInformation
.map(\.handle.value)
switch response.choice.answer {
case .approval:
for value in handleValues {
approvedHandles.insert(value)
deniedHandles.remove(value)
}
case .denial:
for value in handleValues {
deniedHandles.insert(value)
}
@unknown default:
break
}
}
}Request permission for multiple contacts in a single question.
func requestGroupPermission(
contacts: [Contact],
in viewController: UIViewController
) async throws {
let personInfoList = contacts.map { contact in
CommunicationTopic.PersonInformation(
handle: CommunicationHandle(
value: contact.identifier,
kind: .custom
),
nameComponents: contact.nameComponents,
avatarImage: contact.avatarCGImage
)
}
let topic = CommunicationTopic(
personInformation: personInfoList,
actions: [.message, .audioCall, .videoCall]
)
let question = PermissionQuestion<CommunicationTopic>(
communicationTopic: topic
)
// Check question properties
print("Question ID: \(question.id)")
print("Choices: \(question.choices.map(\.title))")
print("Default choice: \(question.defaultChoice.title)")
if let expiration = question.expirationDate {
print("Expires: \(expiration)")
}
try await AskCenter.shared.ask(question, in: viewController)
}Check handles before building the permission UI.
@Observable
@MainActor
final class ContactListViewModel {
var contacts: [ContactItem] = []
struct ContactItem: Identifiable {
let id: String
let name: String
let handle: CommunicationHandle
var isKnown: Bool = false
var needsPermission: Bool = false
}
func refreshContactStatus() async {
let limits = CommunicationLimits.current
let allHandles = Set(contacts.map(\.handle))
let knownHandles = await limits.knownHandles(in: allHandles)
for i in contacts.indices {
contacts[i].isKnown = knownHandles.contains(contacts[i].handle)
contacts[i].needsPermission = !contacts[i].isKnown
}
}
}Build a complete permission flow in SwiftUI.
import SwiftUI
import PermissionKit
struct ContactDetailView: View {
let contact: Contact
@State private var permissionState: PermissionState = .unknown
@Environment(PermissionManager.self) private var permissionManager
enum PermissionState {
case unknown, checking, needsPermission, approved, denied, error(String)
}
var body: some View {
VStack {
Text(contact.name)
.font(.title)
switch permissionState {
case .unknown, .checking:
ProgressView("Checking permissions...")
case .needsPermission:
let handle = CommunicationHandle(
value: contact.phoneNumber,
kind: .phoneNumber
)
let question = PermissionQuestion<CommunicationTopic>(
handle: handle
)
VStack {
Text("Permission needed to message this contact.")
.foregroundStyle(.secondary)
PermissionButton(question: question) {
Label("Ask to Message", systemImage: "message.badge.clock")
}
.buttonStyle(.borderedProminent)
}
case .approved:
Label("Messaging enabled", systemImage: "checkmark.circle.fill")
.foregroundStyle(.green)
case .denied:
Label("Permission denied", systemImage: "xmark.circle.fill")
.foregroundStyle(.red)
case .error(let message):
Label(message, systemImage: "exclamationmark.triangle")
.foregroundStyle(.orange)
}
}
.task {
await checkPermission()
}
}
private func checkPermission() async {
permissionState = .checking
let handle = CommunicationHandle(
value: contact.phoneNumber,
kind: .phoneNumber
)
let limits = CommunicationLimits.current
let isKnown = await limits.isKnownHandle(handle)
if isKnown {
permissionState = .approved
} else if permissionManager.isApproved(contact.phoneNumber) {
permissionState = .approved
} else if permissionManager.isDenied(contact.phoneNumber) {
permissionState = .denied
} else {
permissionState = .needsPermission
}
}
}On macOS, pass an NSWindow instead of UIViewController.
#if os(macOS)
import AppKit
import PermissionKit
func requestPermission(
for question: PermissionQuestion<CommunicationTopic>,
in window: NSWindow
) async throws {
try await AskCenter.shared.ask(question, in: window)
}
#endifProvide actionable recovery for each error type.
func handleAskError(_ error: AskError) -> (title: String, message: String, action: (() -> Void)?) {
switch error {
case .communicationLimitsNotEnabled:
return (
"No Restrictions",
"Communication limits are not enabled. You can communicate freely.",
nil
)
case .contactSyncNotSetup:
return (
"Contact Sync Required",
"Please enable contact sync in Settings to use this feature.",
{ openContactSyncSettings() }
)
case .invalidQuestion:
return (
"Invalid Request",
"The permission request could not be created. Please try again.",
nil
)
case .notAvailable:
return (
"Not Available",
"This feature is not available on this device.",
nil
)
case .systemError(let underlying):
return (
"System Error",
underlying.localizedDescription,
nil
)
case .unknown:
return (
"Unknown Error",
"An unexpected error occurred. Please try again later.",
nil
)
@unknown default:
return (
"Error",
"An error occurred.",
nil
)
}
}skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references