Search code examples
iosswiftxcodeswiftuipush-notification

"Push Notifications" toggle that can detect app's current authorization?


I'd like to add a versatile "Push Notifications" toggle to my SettingsView.

By versatile, I mean I'd like for the toggle to be automatically set to enabled if the app's notification current permission is authorized, provisional, or ephemeral. And disabled if denied or not determined.

Ideally, the toggle would also request permission if set to "not determined." And send to App Settings if set to "denied."

Snippet of my SettingsView

struct SettingsView: View {
    let notify = NotificationHandler()
    @State private var enablePushNotifications = false

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Notifications")) {
                    let toggle = Binding<Bool> (
                        get: { self.enablePushNotifications }
                        set: { newValue in
                            self.enablePushNotifications = newValue
                            })

                    Toggle("Push Notifications", isOn: toggle)
             }
        }
    }
}

Solution

  • You cannot programmatically "unauthorised" yourself from notifications, so I assume you have a flag stored in @AppStorage or somewhere else that indicates whether the user prefers to have notifications.

    @AppStorage("notifications") var shouldSendNotifications = true
    

    Only when shouldSendNotifications is true and the user has authorised your app, should you send notifications.

    Then, all you need to do is to get the current authorisation status using notificationSettings, and update some @State with the status you got. You could run this code in a task(id:) block, so that you can re-run it just by changing the id.

    @preconcurrency import UserNotifications
    
    struct ContentView: View {
        @State private var notificationAuthorised = false
        @State private var taskTrigger = false
        @Environment(\.scenePhase) var scenePhase
        @AppStorage("notifications") var shouldSendNotifications = true
        
        var body: some View {
            Form {
                Section(header: Text("Notifications")) {
                    let binding = Binding {
                        notificationAuthorised && shouldSendNotifications
                    } set: { newValue in
                        if newValue { // turning on notifications
                            Task {
                                // perhaps also UIApplication.shared.registerForRemoteNotifications()
                                shouldSendNotifications = true
    
                                let notifCenter = UNUserNotificationCenter.current()
                                let settings = await notifCenter.notificationSettings()
                                if settings.authorizationStatus == .notDetermined {
                                    // show the request alert
                                    try await notifCenter.requestAuthorization(options: [.alert, .sound, .badge])
                                } else if settings.authorizationStatus == .denied {
                                    // go to settings page
                                    if let appSettings = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(appSettings) {
                                        await UIApplication.shared.open(appSettings)
                                    }
                                }
                                // run the task again to update notificationAuthorised
                                taskTrigger.toggle()
                            }
                        } else {
                            shouldSendNotifications = false
                            // perhaps also UIApplication.shared.unregisterForRemoteNotifications()
                        }
                    }
    
                    Toggle("Push Notifications", isOn: binding)
                }
            }
            .task(id: taskTrigger) {
                let notifCenter = UNUserNotificationCenter.current()
                let settings = await notifCenter.notificationSettings()
                switch settings.authorizationStatus {
                case .notDetermined, .denied:
                    notificationAuthorised = false
                case .authorized, .ephemeral, .provisional:
                    notificationAuthorised = true
                @unknown default:
                    notificationAuthorised = false
                }
            }
            // run the task again to update notificationAuthorised when coming back to the app
            .onChange(of: scenePhase) {
                taskTrigger.toggle()
            }
        }
    }