NotificationPermissionObserver
Tracks push notification permission status. Re-checks on every app foreground event and streams changes in real time.
NotificationPermissionStatus
An enum representing all possible push notification authorization states.
public enum NotificationPermissionStatus: Sendable {
case notDetermined
case denied
case authorized
case provisional
case ephemeral
} NotificationPermissionObserving Protocol
The protocol provides the current status and a stream. Since iOS provides no system callback when the user changes notification permission in Settings, the observer re-checks the status on every app foreground event.
public protocol NotificationPermissionObserving: Sendable {
/// The current permission status (synchronous read)
var status: NotificationPermissionStatus { get }
/// Stream of permission status changes. Emits the current value immediately
/// on subscription, then re-checks on every app foreground event since iOS
/// provides no callback when the user changes permission in Settings.
var statusStream: AsyncStream<NotificationPermissionStatus> { get }
/// Forces a fresh check against UNUserNotificationCenter.
/// Called automatically on app foreground.
func refresh() async
} Helper Extensions
The protocol includes convenience computed properties for the most common permission states.
// Convenience extensions on NotificationPermissionObserving
extension NotificationPermissionObserving {
/// true when status is .authorized, .provisional, or .ephemeral
var isGranted: Bool { get }
/// true when status is .denied
var isDenied: Bool { get }
/// true when status is .notDetermined
var canRequestPermission: Bool { get }
}
// Note: For requesting permission, use PushPermission from ForgePush. Note — For requesting permission, use PushPermission from ForgePush. NotificationPermissionObserver is observation-only.
Usage
import ForgeObservers
let notifications = NotificationPermissionObserver()
// Check current status
print("Status: \(notifications.status)")
print("Granted: \(notifications.isGranted)")
print("Can request: \(notifications.canRequestPermission)")
// Subscribe to changes
Task {
for await status in notifications.statusStream {
print("Permission status: \(status)")
}
} Reacting to Changes
The stream emits the current value immediately on subscription, then re-emits each time the status changes. Use this to drive UI that reflects the current permission state without polling.
// React to permission changes as they happen.
// The stream emits the current value immediately on subscription,
// then re-emits whenever the status changes (e.g. after returning from Settings).
func observePermissionChanges() async {
let notifications: NotificationPermissionObserving = // resolve from DI
for await status in notifications.statusStream {
switch status {
case .authorized, .provisional, .ephemeral:
enableNotificationFeatures()
case .denied:
showPermissionDeniedBanner()
case .notDetermined:
break // Permission not yet requested — use ForgePush's PushPermission to request
}
}
} ViewModel Example
Bind notification permission state to a ViewModel for use in settings screens.
@Observable
final class TaskFlowSettingsViewModel {
private let notifications: NotificationPermissionObserving
var isGranted = false
var isDenied = false
var canRequest = false
init(notifications: NotificationPermissionObserving) {
self.notifications = notifications
// Sync initial state synchronously before stream starts
isGranted = notifications.isGranted
isDenied = notifications.isDenied
canRequest = notifications.canRequestPermission
}
func startObserving() async {
for await status in notifications.statusStream {
isGranted = notifications.isGranted
isDenied = notifications.isDenied
canRequest = notifications.canRequestPermission
}
}
/// Force a re-check (e.g. after returning from a deep link to Settings).
func refresh() async {
await notifications.refresh()
}
}