Home > Software engineering >  SwiftUI Create property with the application's local notification .authorizationStatus
SwiftUI Create property with the application's local notification .authorizationStatus

Time:05-14

I'm looking to create a property that will track the user's notification authorization setting for the application. (The ultimate goal here is to alert the user if they ask for a notification but have declined to receive them from the app.) I've tried a variety of solutions, to no avail. The code below throws an error that says "Cannot convert return expression of type 'Void' to return type 'String'".

class LocalNotificationScheduler {
var notificationAuthStatus: String {
                UNUserNotificationCenter.current().getNotificationSettings { settings in
                    switch settings.authorizationStatus {
                        case .authorized:
                            return "authorized"
                        case .provisional:
                            return "provisional"
                        case .notDetermined:
                            return "notDetermined"
                        case .denied:
                           return "denied"
                        default:
                            break
                    } // End of "switch settings.authorizationStatus {"
                } // End of "{ settings in"
            }
             private func requestAuthorization() {
                UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
                    if granted == true && error == nil {
                        self.scheduleNotification()
                    }
                }
            } // End of requestAuthorization() func
            
            func schedulingRequested() {
                UNUserNotificationCenter.current().getNotificationSettings { settings in
                    switch settings.authorizationStatus {
                        case .notDetermined:
                            self.requestAuthorization()
                        case .authorized, .provisional:
                            self.scheduleNotification()
                        case .denied:
                            print("Conflict between request for notification and app permissions!")
                        default:
                            break
                    }
                }
            } // End of schedule() func
            
            private func scheduleNotification() {
                let content = UNMutableNotificationContent()
                content.title = "Scheduled notification"
                content.body = "Test"
                content.sound = .default
                
                // Trigger
                let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
                let request = UNNotificationRequest(identifier: "test", content: content, trigger: trigger)
                
                UNUserNotificationCenter.current().add(request) { error in
                    guard error == nil else { return }
                }
            } // End of scheduleNotification() func
            
            // MARK: - Life Cycle
            init() {
            } // End of init()
             
        } 
    
    struct ContentView: View {
        
        // MARK: - Properties
        // Alert the user that there's a conflict between their request for a notification and the app permissions
        @State var notificationConflict: Bool = false
        
        // MARK: - View
        var body: some View {
            Form {
                Button("Send notification") {
                    
                    let notificationScheduler = LocalNotificationScheduler()
                    print("Step 1: notificationScheduler.notificationAuthStatus = \(notificationScheduler.notificationAuthStatus)")
                    
                    if notificationScheduler.notificationAuthStatus == "denied" {
                        notificationConflict = true
                        print("Step 2a: notificationScheduler.notificationAuthStatus WAS denied so...")
                        print("notificationConflict = \(notificationConflict)")
                    } else {
                        print("Step 2b: notificationScheduler.notificationAuthStatus was NOT denied so scheduling notification")
                        notificationScheduler.schedulingRequested()
                    }
                    print("Step 3: notificationScheduler.notificationAuthStatus = \(notificationScheduler.notificationAuthStatus)")
                    print("Step 3: notificationConflict = \(notificationConflict)")
                }
            } // End of Form
        } // End of body view
    }

I've also tried to creating a function that looks a lot like the code above but sets the value of notificationAuthStatus rather than returning it. The function doesn't throw an error, but it runs asynchronously such that the value of notificationAuthStatus is not set when I need to use it.

Any advice about how to proceed is much appreciated!

CodePudding user response:

This compiles:

var notificationAuthStatus: String {
    var status = "notDetermined"
    UNUserNotificationCenter.current().getNotificationSettings { settings in
        switch settings.authorizationStatus {
        case .authorized:
            status = "authorized"
        case .provisional:
            status = "provisional"
        case .notDetermined:
            status = "notDetermined"
        case .denied:
            status = "denied"
        default:
            break
        } // End of "switch settings.authorizationStatus {"
    } // End of "{ settings in"
    return status
}

but I don't know if this suffices for your code as you don't show it in context, so we don't know how you are using it.

If you are using it in a notification controller class to see if you have authorization, or need to request authorization, why not jus use it directly instead of returning a string?

CodePudding user response:

I think I've come up with a solution that works for my purposes. I'm posting it here in case anyone else has the same challenge and stumbles upon this question. Here's what worked for me:

  1. Determine the notification .authorizationStatus using a method, rather than a computed property.
  2. Call that method both when an instance of the LocalNotificationScheduler class is initialized and when a notification is requested. This combination seems to handle midstream changes to the notification .authorizationStatus if the user changes their settings.
  3. Initialize the LocalNotificationScheduler class when the view loads, rather than when the user requests a notification. This means that there may be some unnecessary overhead for the app - creating an instance of LocalNotificationScheduler when it's not needed - but it also accommodates the time lag in checking the .authorizationStatus, which can cause properties using .authorizationStatus not to update immediately.

Here's my LocalNotificationScheduler code:

class LocalNotificationScheduler {
    
    var notificationAuthStatusDenied: Bool = false
    
    // MARK: - Methods
    func determineAuthStatus() {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            switch settings.authorizationStatus {
                case .authorized, .provisional, .notDetermined:
                    self.notificationAuthStatusDenied = false
                case .denied:
                   self.notificationAuthStatusDenied = true
                default:
                    break
            } // End of "switch settings.authorizationStatus {"
        } // End of "{ settings in"
    } // End of determineAuthStatus() func
    
    private func requestAuthorization() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
            if granted == true && error == nil {
                self.scheduleNotification()
            }
        }
    } // End of requestAuthorization() func
    
    func schedulingRequested() {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            switch settings.authorizationStatus {
                case .notDetermined:
                    self.requestAuthorization()
                case .authorized, .provisional:
                    self.scheduleNotification()
                case .denied:
                    print("Conflict between request for notification and app permissions!")
                default:
                    break
            }
        }
    } // End of schedule() func
    
    private func scheduleNotification() {
        let content = UNMutableNotificationContent()
        content.title = "Scheduled notification"
        content.body = "Test"
        content.sound = .default
        
        // Trigger
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
        let request = UNNotificationRequest(identifier: "test", content: content, trigger: trigger)
        
        UNUserNotificationCenter.current().add(request) { error in
            guard error == nil else { return }
        }
    } // End of scheduleNotification() func
    
    // MARK: - Life Cycle
    init() {
        determineAuthStatus()
    } // End of init()
}

And the View code:

struct ContentView: View {

// MARK: - Properties
var notificationScheduler = LocalNotificationScheduler()

// Alert the user that there's a conflict between their request for a notification and the app permissions
@State var notificationConflictAlertNeeded: Bool = false

// MARK: - View
var body: some View {
    Form {
        Button("Send notification") {
        
            // Re-check .authorizationStatus - this doesn't complete until the code below has run,
            // hence the need to first check .authorization status when the notificationScheduler property is initialized.
            // This does mean that the app re-checks the .authorizationStatus and can detect a change during app use
            notificationScheduler.determineAuthStatus()
            
            // Either schedule the notification or set the flag indicating that this isn't possible
            if notificationScheduler.notificationAuthStatusDenied == false {
                notificationScheduler.schedulingRequested()
            } else {
                notificationConflictAlertNeeded = true
            }
            
        }
    } // End of Form
} // End of body view
}

So, that's my current solution. If there's a better way to do all this, I'd welcome the input!

  • Related