How do local and remote notifications work with UserNotifications: authorization, triggers, categories, and interruption levels?

17.1.1 · L3 · Interview

Predict first

A daily 9:30 calendar trigger and a one-shot interval trigger: what do they report?

import UserNotifications
import Foundation

var dc = DateComponents()
dc.hour = 9; dc.minute = 30
let cal = UNCalendarNotificationTrigger(dateMatching: dc, repeats: true)
let c = Calendar.current.dateComponents([.hour, .minute], from: cal.nextTriggerDate()!)
print(c.hour!, c.minute!, cal.repeats)
let t = UNTimeIntervalNotificationTrigger(timeInterval: 30, repeats: false)
print(t.timeInterval, t.repeats)
  • 9 30 true, then 30.0 false
  • 9 0 true, then 30.0 true
  • 0 0 false, then 0.0 false

Predict first

A repeating interval trigger with a short interval:

import UserNotifications

let t = UNTimeIntervalNotificationTrigger(timeInterval: 30, repeats: true)
print(t.timeInterval)
  • It is fine and repeats every 30 seconds
  • It raises an exception: repeating intervals must be at least 60 seconds

Predict first

What are the raw values of the authorization status and the time-sensitive level?

import UserNotifications

let c = UNMutableNotificationContent()
c.interruptionLevel = .timeSensitive
print(c.interruptionLevel.rawValue, UNAuthorizationStatus.provisional.rawValue, UNAuthorizationStatus.notDetermined.rawValue)
  • 2 3 0
  • 1 2 0
  • 3 4 1

Mental model. Permission to show notifications is a front-door key the user can take back at any time. The trigger is the alarm clock you set, but the system keeps the alarm on its own shelf, with a limit of sixty-four.

Everything goes through UNUserNotificationCenter.current(). First authorization: requestAuthorization(options:) (alert, sound, badge, and optionally .provisional, .criticalAlert with a special entitlement) shows the system prompt once; afterwards the status (notDetermined 0, denied, authorized, provisional 3, ephemeral) can only be changed by the user in Settings, so ask in context, after the user understands the value, and read notificationSettings() rather than assuming. Provisional authorization delivers quietly to Notification Center with no prompt, a way to earn trust first. A local notification is a UNNotificationRequest with content (UNMutableNotificationContent: title, body, sound, badge, userInfo, categoryIdentifier, interruptionLevel and threadIdentifier for grouping) and a trigger: UNTimeIntervalNotificationTrigger (after N seconds; a repeating one must be at least 60 seconds, or UIKit raises an exception), UNCalendarNotificationTrigger (at a calendar time, for example every day at 9:30, whose nextTriggerDate() you can inspect) and UNLocationNotificationTrigger (on entering or leaving a region). The system keeps at most 64 pending local notifications per app, so schedule the nearest ones and top up.

Actions and categories: register UNNotificationCategory objects with UNNotificationActions (buttons, text input) and set the content’s categoryIdentifier; the user’s tap arrives in userNotificationCenter(_:didReceive:withCompletionHandler:) on the delegate, which you must set early (at launch, before the app finishes starting) so a tap that launched the app is delivered. willPresent decides whether a notification shows while the app is in the foreground (by default it does not). Interruption levels (iOS 15): .passive (no sound or wake), .active (default), .timeSensitive (breaks through Focus and the scheduled summary, needs the Time Sensitive Notifications capability) and .critical (overrides mute, entitlement required). Use the lowest level that fits; abusing it trains users to turn you off. Notification service extensions modify a remote push before display (download an image, decrypt text), and content extensions provide custom UI. Remote pushes need the registration step (registerForRemoteNotifications(), then the device token in the app delegate callback, sent to your server), which is a separate matter from the user’s permission to show them.

The calendar trigger’s next fire date is the next 09:30, and an interval trigger remembers its seconds and repeat flag.

UserNotifications enforces a minimum of 60 seconds for repeating time-interval triggers.

Time sensitive is level 2, provisional is status 3 and a fresh app is notDetermined (0).

Common traps

  • Asking for permission on first launch with no context. The one-time prompt is wasted; ask when the value is clear, or start with provisional.
  • Setting the delegate late. A tap that cold-launches the app is delivered before you were listening.

They might follow up with

  • How many pending local notifications can an app have? 64; schedule the nearest ones and replenish.
  • What does .timeSensitive need? The Time Sensitive Notifications capability, and it should be used only for genuinely urgent items.

Related questions

  • 17.1.2 How does the Apple Push Notification service deliver a push: tokens, priority, collapse, expiry, and what can…
  • 17.7.1 How do you approach an unfamiliar Apple framework: what do you check before you write code, and how do you…

How does the Apple Push Notification service deliver a push: tokens… →