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:
- Determine the notification .authorizationStatus using a method, rather than a computed property.
- 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.
- 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!