📦 SwiftyLaunch Modules
🔗 SharedKit
Request Permissions to Camera, Microphone, Location, etc

Request Permission to access the Camera, Microphone, Location, etc.

SharedKit (SwiftyLaunch Module) - Requesting Permissions

SwiftyLaunch's SharedKit comes with a handy integrated way to ask users for permissions to access their Camera, Microphone, Location, and more. This is a common task in many apps and SharedKit makes it easy to do so.

RequestType enum

The RequestType enum defines all the different Capabalities the user can be asked to grant access to. The following types are available out of the box:

RequestType.swift
public enum RequestType: Identifiable {
    case photosAccess
    case cameraAccess
    case microphoneAccess
    case locationAccess
    case contactsAccess
    case calendarAccess
    case remindersAccess
}

These types describe access to specific system resources and require the user's permission to access them.

Problem with Permissions

When asking for permissions, it is important to remember that the user can deny access to the requested resource, and that the app should handle this case gracefully. Additionally, in most cases we can only ask for permissions once, so it is important to be able to clearly tell the user that they should grant access to the requested resource in app's settings.

So, we have to keep following things in mind:

  • We have to ask the user for permissions to access specific resources.
  • We can only do it once.
  • If the user denies access, we have to handle this case gracefully.
  • The user can always revoke access in the app's settings.

To address this, we have designed a simple way to ask for permissions in SharedKit, using closures, notification center and SwiftUI's view modifiers.

Basic Usage Example

Here's a common example on how to ask the user for microphone access:

Microphone Access Example

In this example we have the haveMicrophonePermission boolean variable, which holds the information, whether the user has granted us access to the microphone or not. By default it is set to false.

As soon as the view appears, we fetch the permission status and set the haveMicrophonePermission variable accordingly. Note, that we can't set it in init, as we can't use async functions in init.

Then, there is a button, which asks the user for microphone access with two closures. The first one is executed if the user grants us access, the second one is executed if the user denies access. We use these closures to set the haveMicrophonePermission variable accordingly.

By pressing on the button, a system dialog appears asking the user for microphone access. Depending on the user's choice, the first or the second closure is executed.

If we already have access to the microphone, pressing on the button will straight up execute the executeIfGotAccess closure.

import SwiftUI
import SharedKit
 
struct TestPermissions: View {
 
    @State private var haveMicrophonePermission: Bool = false
 
    var body: some View {
        VStack {
            Text("Got Permission: \(haveMicrophonePermission ? "Yes" : "No")")
 
            Button("Ask For Microphone Access") {
                askUserFor(.microphoneAccess, executeIfGotAccess: {
                    haveMicrophonePermission = true
                }, onDismiss: {
                    haveMicrophonePermission = false
                })
            }
        }
        .onAppear {
            Task {
                let permissionStatus = await RequestType.microphoneAccess.permissionStatus()
                haveMicrophonePermission = (permissionStatus == .gotPermission)
            }
        }
    }
}

What happens if the user presses on allow in the sheet, but denies the system prompt?

If the user presses on allow, but denies the system prompt, the access sheet will automatically be dismissed.

As we can only show the user the system prompt once, we will have to refer the user to grant us access in app's settings.

The provided sheet knows that out of the box and replaces the button that shows the system prompt with a button that redirets the user to the settings panel.

Redirect to Settings Example

Requiring Permissions to display a View

Another common use case would be to require the user to grand access to a specific resource to display a view.

An example could be the requirement to user location to show a map view, or to require camera access to show camera preview view.

For this, we would use the .requireCapabilityPermission() view modifier, which takes a RequestType as an argument.

This is exactly what we use to power our AI Vision Example in AIKit. We display a camera feed, but only if the user has granted us access to the camera.

AIVisionExampleView.swift
import SwiftUI
import SharedKit
// Other imports
 
struct AIVisionExampleView: View {
 
    // ...
 
    /// View Model to control this View
    @StateObject private var vm = AIVisionExampleViewModel()
 
    /// Interact with the camera
    @StateObject private var cameraViewModel = CameraViewModel()
 
    // ...
 
    VStack(spacing: 25) {
 
        CameraView()
            .requireCapabilityPermission(
                of: .cameraAccess,
                onSuccess: {
                    // got access to camera -> start feed
                    vm.gotCameraPermissions(cameraVM: cameraViewModel)
                },
            )
 
        HStack {
            // ...
            Button {
                // Once the user presses the button and releases it, we tell that to our ViewModel
                vm.releasedShutterButton(cameraVM: cameraViewModel, voiceRecordingVM: voiceRecordingViewModel)
            } label: {
                Circle()
                    .fill(.white)
                    .frame(width: 75, height: 75)
            }
            .buttonStyle(ShutterButton())
            // ...
        }
 
    }
}

This results in an inline permission request view, which will be shown instead of the feed if the user hasn't granted us access to the camera yet.

Inline Permission Request Example

Relevant Functions, Variables and Modifiers

Attached to RequestType enum

As discussed before, it defines all the different Capabalities the user can be asked to grant access to.

Current Permission Status

Async function will return the current permission status of the requested capability. An example of the photosAccess case is shown below:

RequestType.swift
public enum RequestType: Identifiable {
    @MainActor public var permissionStatus: () async -> RequestPermissionStatus {
        switch self {
            // ... other cases
            case .photosAccess:
                return {
                    let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)
                    switch status {
                    case .notDetermined:
                        return .notYetAsked
                    case .restricted:
                        return .denied
                    case .denied:
                        return .denied
                    case .authorized:
                        return .gotPermission
                    case .limited:  // means that we do have access to the library, but just not all of it.
                        return .gotPermission
                    @unknown default:  // This is for future proofing, if Apple adds more cases.
                        return .denied
                    }
                }
            // ... other cases
        }
    }
}

The RequestPermissionStatus enum is defined as follows:

RequestType.swift
public enum RequestPermissionStatus {
 
    /// Means the user has not yet given permission
    case notYetAsked
 
    /// Means the user has already given permission
    case gotPermission
 
    /// Means the user has denied and we  should send the user to the settings
    case denied
}

Usage example:

import SharedKit
 
let permissionStatus = await RequestType.photosAccess.permissionStatus()

Globally defined

Ask User for Permission

This function will show user a sheet if the user hasn't granted us access to the requested capability yet, and will execute the executeIfGotAccess closure if the user grants us access, or the onDismiss closure if the user denies access.

askUserFor.swift
public func askUserFor(_ type: RequestType, executeIfGotAccess: (() -> Void)? = nil, onDismiss: (() -> Void)? = nil) { }

Usage example:

import SharedKit
 
askUserFor(.locationAccess, executeIfGotAccess: {
    // do something if we got access
}, onDismiss: {
    // do something if we didn't get access
})

This function works by calling sending a notification to the notification center, which is then picked up by the ShowRequestSheetWhenNeededModifier() view modifier, which is attached to the root view of the app. It listens for the notification and shows the sheet if needed (if the user has not granted permission yet).

Require Capability Permission View Modifier

If you want to lock a view behind a permission request, you can use the .requireCapabilityPermission() view modifier.

requireCapabilityPermission.swift
extension View {
    /// This modifier makes sure that the view that it is applied to will only be shown if the user has granted us with the required permissions
    public func requireCapabilityPermission(
        of permissionRequirement: RequestType,
        onSuccess: @escaping () -> Void = {},
        onCancel: @escaping () -> Void = {}
    ) -> some View {
        modifier(RequireCapabilityPermissionViewModifier(permissionRequirement: permissionRequirement, onSuccess: onSuccess, onCancel: onCancel))
    }
}

You can attach it to any view that might need special capabilities to be of use. It will replace the view with a permission request view if the user hasn't granted us access to the requested capability yet.

You pass to the view modifier the capability you want to ask for, and optionally two additional closures that will be executed if the user grants us access or denies access.

See the example above for usage.

Customizing the Permission Request Sheet Text

It is recommended to customize the text of the permission request sheet to tell the user why exactly you need access to the requested capability.

To do so, you have to set the permission message in two places:

Project's Info.plist

In your project's Info.plist file, you have to set the NSCameraUsageDescription, NSMicrophoneUsageDescription, NSLocationWhenInUseUsageDescription, etc. keys. This is the text that will be shown in the system prompt.

Info.plist Permissions

In RequestType.swift

Edit the data variable in the RequestType enum to the information that you want to be shown in the permission request sheet.

RequestType.swift
public enum RequestType: Identifiable {
 
    // ...
 
    /// Ask the user for photo library access
    case photosAccess
 
    // ...
 
    public var data: RequestTypeData {
        switch self {
        // ...
        case .photosAccess:
            return RequestTypeData(
                sfSymbolName: "photo.on.rectangle.angled",
                title: "Allow Access to Photos?",
                subtitle: "With Access to your Photo\nLibrary, we can do cool stuff.",
                footerNote: "We won't spy on you, we promise.",
                ctaText: "Allow Access to Photos"
            )
        // ...
        }
    }
}