Repeating Alerts like Messages
Messages will repeat using notification to alert us about new messages many times1 2 to ensure that we are not missing new messages. Today, let’s talk about how to implement it by ourselves.
Suppose all we want to repeat is a remote notification. In that case, all we need to do is removing any keys that would trigger user interactions from the payload to silence the notification and scheduling a local notification along with a repeating notification created by a UNTimeIntervalNotificationTrigger
.
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60 * 5, repeats: true)
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: nil)
let repeatingRequest = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
let userNotificationCenter = UNUserNotificationCenter.current()
userNotificationCenter.add(request) { error in
...
}
userNotificationCenter.add(repeatingRequest) { error in
...
}
Don’t forget to remove repeatingRequest
after the user responded to the notification or launched the app.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let identifier = response.notification.request.identifier
center.removePendingNotificationRequests(withIdentifiers: [identifier])
}
A limit of UNTimeIntervalNotificationTrigger
for repeating notifications is that the timeInterval
must be 60 seconds or greater.
But how to repeat a notification created by UNCalendarNotificationTrigger
?
The repeats
in the initialization means to reschedule the notification request each time the notification is delivered to the next cycle, which has the same dateComponents
, so it’s not what we want.
We have to change the approach. Add multiple requests to simulate the behavior of a repeating notification.
...
let calendar = Calendar.current
for offsetCount in 0 ..< 10 {
let date = Calendar.current.date(byAdding: .minute, value: offsetCount * 5, to: someDate)!
var dateComponents = Calendar.current.dateComponents(in: .current, from: date)
// http://www.openradar.me/35247464
dateComponents.quarter = nil
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let repeatingRequest = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
userNotificationCenter.add(repeatingRequest) { error in
...
}
}
Only the last repeatingRequest
was fulfilled. It seems like we cannot use the same identifier for every repeatingRequest
.
So let’s use a different identifier for each repeatingRequest
.
let repeatingRequest = UNNotificationRequest(identifier: "\(uuidString)@\(date.timeIntervalSince1970.description)",
content: content, trigger: trigger)
Also, update the code to remove it accordingly.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let uuidString = response.notification.request.identifier.split(separator: "@").first!
center.getPendingNotificationRequests { requests in
let identifiers = requests
.map(\.identifier)
.filter { $0.starts(with: uuidString) }
center.removePendingNotificationRequests(withIdentifiers: identifiers)
}
}
It just works, and it’s even able to break the limit of 60 seconds. However, there are some side effects of this approach.
Since we are using different identifiers, every notification is count as a new one. Therefore, we should consider using threadIdentifier
to group related notifications.
Besides, the system only keeps the soonest 64 requests3, so we should add requests according to their priority.
For example, cut down the repeat times.
userNotificationCenter.getPendingNotificationRequests { requests in
let remaining = 64 - requests.count
guard remaining > 0 else {
// Drop the most unimportant request or let the user decide
return
}
for offsetCount in 0 ..< min(remaining, 10) {
...
}
}
Conclusions
For a remote notification, use UNTimeIntervalNotificationTrigger
to repeat.
For a notification to be delivered at a specific date and time, also schedule the following notifications to make it look repeating. Be careful of the limit of the number of notification requests.
-
We can change the setting from Settings > Notifications > Messages > Repeat Alerts. ↩
-
This feature is not working on some of my devices. ↩
-
It’s only documented in UILocalNotification, but it seems still correct. ↩