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."
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)
}
}
}
}
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()
}
}
}