PermissionKit is a centralized, robust framework designed to make handling iOS permissions seamless using async/await, Combine, and SwiftUI.
- iOS 15.0+
- macOS 12.0+
- Swift 6.0+
IMPORTANT: Since this is a Swift Package, you must manually add the required Privacy usage description keys to your application's
Info.plistfor any permissions you intend to use. Failure to do so will cause your app to crash when requesting the permission.
| Permission | Info.plist Key Required |
|---|---|
.camera |
NSCameraUsageDescription |
.photoLibrary |
NSPhotoLibraryUsageDescription |
.location |
NSLocationWhenInUseUsageDescription (and/or Always) |
.contacts |
NSContactsUsageDescription |
.calendar |
NSCalendarsUsageDescription |
.microphone |
NSMicrophoneUsageDescription |
.notifications |
None required |
.bluetooth |
NSBluetoothAlwaysUsageDescription |
.motion |
NSMotionUsageDescription |
.faceID |
NSFaceIDUsageDescription |
- One-line permission request: Simple
async/awaitAPI to request access. - Permission status: Easily check
.authorized,.denied, etc. - Open Settings helper: Direct users to settings if they denied permission.
- Custom dialogs: Built-in customizable SwiftUI dialog modifier to explain why a permission is needed.
- Multiple permission requests: Request an array of permissions sequentially.
- Combine support: Subscribe to
statusDidChangeto reactively update your UI when permissions change.
PermissionKit/
├── Sources/PermissionKit/
│ ├── Managers/ (PermissionManager)
│ ├── Permissions/ (CameraPermission, LocationPermission, etc.)
│ ├── Models/ (PermissionType, PermissionStatus)
│ ├── Extensions/ (View+Permissions)
│ ├── Views/ (PermissionDialogView)
│ └── Utilities/ (SettingsHelper)
├── Tests/PermissionKitTests/
└── Package.swift
Use PermissionManager to asynchronously request any supported permission:
import PermissionKit
Task {
let status = await PermissionManager.request(.camera)
if status == .authorized {
print("Camera access granted!")
} else {
print("Camera access denied.")
}
}if PermissionManager.isGranted(.camera) {
// Continue with camera setup
} else {
// Show permission rationale or error
}Task {
let results = await PermissionManager.requestMultiple([.camera, .microphone])
let cameraStatus = results[.camera]
}Listen to global permission state changes to update your views reactively:
import Combine
import PermissionKit
class CameraViewModel: ObservableObject {
@Published var hasCameraAccess = PermissionManager.isGranted(.camera)
private var cancellables = Set<AnyCancellable>()
init() {
PermissionManager.shared.statusDidChange
.filter { $0 == .camera }
.receive(on: RunLoop.main)
.sink { [weak self] _ in
self?.hasCameraAccess = PermissionManager.isGranted(.camera)
}
.store(in: &cancellables)
}
}Show a rationale dialog before triggering the system prompt:
import SwiftUI
import PermissionKit
struct ContentView: View {
@State private var showPermissionDialog = false
var body: some View {
Button("Use Camera") {
if PermissionManager.isGranted(.camera) {
// Open Camera
} else {
showPermissionDialog = true
}
}
.permissionDialog(
isPresented: $showPermissionDialog,
type: .camera,
title: "Camera Access",
message: "We need access to your camera to take a profile picture."
) { finalStatus in
if finalStatus == .authorized {
// Open Camera
}
}
}
}If a user denied a permission, you can prompt them to open settings:
Button("Open Settings") {
SettingsHelper.openSettings()
}This project is licensed under the MIT License.