macos-permissions
macOS Permissions
Critical Constraints
- ❌ DO NOT assume permissions are granted → ✅ Always check before using protected APIs
- ❌ DO NOT repeatedly prompt after denial → ✅ Guide user to System Settings instead
- ❌ DO NOT block the entire app on missing permission → ✅ Gracefully degrade, offer reduced functionality
- ❌ DO NOT forget PrivacyInfo.xcprivacy → ✅ Required for App Store submission
Permission Types
| Permission | Check API | Required For |
|---|---|---|
| Accessibility | AXIsProcessTrusted() |
Global hotkeys, text insertion, CGEvent |
| Screen Recording | CGPreflightScreenCaptureAccess() |
Screen capture, window list |
| Full Disk Access | Try access + handle error | Reading other app data |
| Camera | AVCaptureDevice.authorizationStatus(for: .video) |
Camera access |
| Microphone | AVCaptureDevice.authorizationStatus(for: .audio) |
Audio capture |
| Location | CLLocationManager().authorizationStatus |
Location services |
| Contacts | CNContactStore.authorizationStatus(for: .contacts) |
Contact access |
Accessibility Permission
import ApplicationServices
// Check without prompting
func isAccessibilityGranted() -> Bool {
AXIsProcessTrusted()
}
// Check and prompt user (shows system dialog)
func requestAccessibilityPermission() -> Bool {
let options = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as String: true]
return AXIsProcessTrustedWithOptions(options as CFDictionary)
}
// Open System Settings → Privacy → Accessibility
func openAccessibilitySettings() {
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")!)
}
Screen Recording Permission
import CoreGraphics
// Check (macOS 15+)
func isScreenRecordingGranted() -> Bool {
CGPreflightScreenCaptureAccess()
}
// Request (macOS 15+)
func requestScreenRecording() -> Bool {
CGRequestScreenCaptureAccess()
}
// Open Settings
func openScreenRecordingSettings() {
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture")!)
}
Camera / Microphone
import AVFoundation
func checkCameraPermission() async -> Bool {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized: return true
case .notDetermined: return await AVCaptureDevice.requestAccess(for: .video)
case .denied, .restricted: return false
@unknown default: return false
}
}
Graceful Degradation Pattern
struct FeatureAvailability {
var canInsertText: Bool { AXIsProcessTrusted() }
var canUseGlobalHotkey: Bool { AXIsProcessTrusted() }
var canCaptureScreen: Bool { CGPreflightScreenCaptureAccess() }
var degradedFeatures: [String] {
var features: [String] = []
if !canInsertText { features.append("Text insertion into other apps") }
if !canUseGlobalHotkey { features.append("Global keyboard shortcuts") }
return features
}
}
struct PermissionBanner: View {
let availability: FeatureAvailability
var body: some View {
if !availability.degradedFeatures.isEmpty {
VStack(alignment: .leading, spacing: 8) {
Label("Some features require permissions", systemImage: "lock.shield")
.font(.headline)
ForEach(availability.degradedFeatures, id: \.self) { feature in
Text("• \(feature)")
.font(.caption)
}
Button("Open System Settings") { openAccessibilitySettings() }
.buttonStyle(.borderedProminent)
}
.padding()
.glassEffect(.regular.tint(.orange), in: .rect(cornerRadius: 12))
}
}
}
Permission Onboarding Flow
struct OnboardingPermissionView: View {
@State private var accessibilityGranted = AXIsProcessTrusted()
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
VStack(spacing: 20) {
Image(systemName: accessibilityGranted ? "checkmark.circle.fill" : "lock.circle")
.font(.system(size: 48))
.foregroundStyle(accessibilityGranted ? .green : .orange)
Text(accessibilityGranted ? "Permission Granted!" : "Accessibility Permission Required")
.font(.title2)
if !accessibilityGranted {
Text("This app needs Accessibility access to insert text into other apps and register global shortcuts.")
.multilineTextAlignment(.center)
Button("Open System Settings") {
requestAccessibilityPermission()
}
.buttonStyle(.borderedProminent)
}
}
.onReceive(timer) { _ in
accessibilityGranted = AXIsProcessTrusted()
}
}
}
Privacy Manifest (PrivacyInfo.xcprivacy)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>CA92.1</string></array>
</dict>
</array>
</dict>
</plist>
Common Mistakes & Fixes
| Mistake | Fix |
|---|---|
| Prompting repeatedly after denial | Check status first, guide to Settings if denied |
| App crashes without permission | Always check before calling protected API |
| User can't find permission setting | Open specific System Settings pane via URL |
| Missing privacy manifest | Add PrivacyInfo.xcprivacy to app bundle |
References
More from makgunay/claude-swift-skills
macos-app-structure
macOS application architecture patterns covering App protocol (@main), Scene types (WindowGroup, Window, Settings, MenuBarExtra), multi-window management, NSApplicationDelegateAdaptor for AppKit lifecycle hooks, Info.plist configuration (LSUIElement for menu bar apps, NSAccessibilityUsageDescription), entitlements for sandbox/hardened runtime, and project structure conventions. Use when scaffolding a new macOS app, configuring scenes and windows, setting up menu bar apps, or resolving macOS-specific lifecycle issues. Corrects the common LLM mistake of generating iOS-only app structures.
31appkit-bridge
Bridging AppKit components into SwiftUI macOS apps. Covers NSViewRepresentable and NSViewControllerRepresentable protocols for hosting AppKit views in SwiftUI, NSHostingView/NSHostingController for hosting SwiftUI in AppKit, NSPanel for floating windows, NSWindow configuration (styleMask, level, collectionBehavior), responder chain integration, NSEvent monitoring (global and local), NSAnimationContext for AppKit animations, NSPopover, NSStatusItem for menu bar, and NSGlassEffectView for AppKit Liquid Glass. Use when SwiftUI lacks a native equivalent, building floating panels, custom window chrome, or integrating legacy AppKit components.
15swiftui-core
Core SwiftUI patterns for macOS and iOS development including navigation (NavigationSplitView, NavigationStack), state management (@State, @Binding, @Environment, @Bindable with @Observable), the new customizable toolbar system (toolbar IDs, ToolbarSpacer, DefaultToolbarItem, searchToolbarBehavior, matchedTransitionSource, sharedBackgroundVisibility), styled text editing (TextEditor with AttributedString, AttributedTextSelection, transformAttributes, textFormattingDefinition), and layout patterns. Use when building any SwiftUI view, implementing navigation, managing state, creating toolbars, or building rich text editors. Corrects common LLM errors like using deprecated NavigationView, wrong state wrappers, and outdated toolbar APIs.
14global-hotkeys
System-wide keyboard shortcut registration on macOS using NSEvent monitoring (simple, app-level) and Carbon EventHotKey API (reliable, system-wide). Covers NSEvent.addGlobalMonitorForEvents and addLocalMonitorForEvents, CGEvent tap for keystroke simulation, Carbon RegisterEventHotKey for system-wide hotkeys, modifier flag handling (.deviceIndependentFlagsMask), common key code mappings, debouncing, Accessibility permission requirements (AXIsProcessTrusted), and SwiftUI .onKeyPress for in-app shortcuts. Use when implementing global keyboard shortcuts, hotkey-triggered panels, or system-wide key event monitoring.
11swiftui-webkit
Native SwiftUI WebKit integration with the new WebView struct and WebPage observable class. Covers WebView creation from URLs, WebPage for navigation control and state management, JavaScript execution (callJavaScript with arguments and content worlds), custom URL scheme handlers, navigation management (load, reload, back/forward), navigation decisions, text search (findNavigator), content capture (snapshots, PDF generation, web archives), and configuration (data stores, user agents, JS permissions). Use when embedding web content in SwiftUI apps instead of the old WKWebView + UIViewRepresentable/NSViewRepresentable bridge pattern. This is a brand new API — do NOT use the old WKWebView wrapping approach.
10liquid-glass
Comprehensive guide to Apple's Liquid Glass design system introduced in macOS 26, iOS 26, and across all Apple platforms. Covers SwiftUI (.glassEffect(), GlassEffectContainer, .interactive(), .tint(), glassEffectID morphing, .buttonStyle(.glass), .buttonStyle(.glassProminent), glassEffectUnion), AppKit (NSGlassEffectView, NSGlassEffectContainerView), UIKit (UIGlassEffect, UIGlassContainerEffect, UIScrollEdgeEffect), and WidgetKit (accented rendering mode, widgetAccentable). Use whenever building UI with the new Apple design language, adopting glass effects, styling buttons or toolbars, or creating modern macOS/iOS interfaces. Always consult this skill when asked about new Apple design.
10