IOS 10.2 UserNotifications problems on simple Alert and Badge
Regarding the alert:
It's possible that your app is still in the foreground when your local notification fires, so you'll need to implement a delegate method in order for the notification to do anything. For instance, defining this method in your delegate will allow the notification to display an alert, make a sound, and update the badge:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert,.badge,.sound])
}
Regarding the badge:
I have observed that creating a UNMutableNotificationContent
object and specifying only the badge value (as an NSNumber object) works for all badge values except 0 (i.e., you cannot clear the badge in this way). I haven't found any documentation for why 0 would behave differently than any other value, especially since the .badge property is defined as an NSNumber?
, so the framework should be able to differentiate between nil
(no change) and 0
(clear the badge).
I have filed a radar against this.
As a work around, I've found that setting the title
property on the UNMutableNotificationContent
object, with a badge value of NSNumber(value: 0)
does fire. If the title
property is missing, it will not fire.
Adding the title property still does not present an alert to the user (Update: this is no longer the case in iOS 11!), so this is a way to silently update the badge value to 0 without needing to invoke the UIApplication object (via UIApplication.shared.applicationIconBadgeNumber = 0
).
Here's the entirety of the code in my example project; there's a MARK in the ViewController code showing where inserting the title property resolves the problem:
//
// AppDelegate.swift
// userNotificationZeroBadgeTest
//
// Created by Jeff Vautin on 1/3/17.
// Copyright © 2017 Jeff Vautin. All rights reserved.
//
import UIKit
import UserNotifications
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (success, error) -> Void in
print("Badge auth: \(success)")
}
// For handling Foreground notifications, this needs to be assigned before finishing this method
let vc = window?.rootViewController as! ViewController
let center = UNUserNotificationCenter.current()
center.delegate = vc
return true
}
}
//
// ViewController.swift
// userNotificationZeroBadgeTest
//
// Created by Jeff Vautin on 1/3/17.
// Copyright © 2017 Jeff Vautin. All rights reserved.
//
import UIKit
import UserNotifications
class ViewController: UIViewController, UNUserNotificationCenterDelegate {
@IBAction func start(_ sender: Any) {
// Reset badge directly (this always works)
UIApplication.shared.applicationIconBadgeNumber = 0
let center = UNUserNotificationCenter.current()
// Schedule badge value of 1 in 5 seconds
let notificationBadgeOneContent = UNMutableNotificationContent()
notificationBadgeOneContent.badge = NSNumber(value: 1)
let notificationBadgeOneTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 1*5, repeats: false)
let notificationBadgeOneRequest = UNNotificationRequest.init(identifier: "1", content: notificationBadgeOneContent, trigger: notificationBadgeOneTrigger)
center.add(notificationBadgeOneRequest)
// Schedule badge value of 2 in 10 seconds
let notificationBadgeTwoContent = UNMutableNotificationContent()
notificationBadgeTwoContent.badge = NSNumber(value: 2)
let notificationBadgeTwoTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 2*5, repeats: false)
let notificationBadgeTwoRequest = UNNotificationRequest.init(identifier: "2", content: notificationBadgeTwoContent, trigger: notificationBadgeTwoTrigger)
center.add(notificationBadgeTwoRequest)
// Schedule badge value of 3 in 15 seconds
let notificationBadgeThreeContent = UNMutableNotificationContent()
notificationBadgeThreeContent.badge = NSNumber(value: 3)
let notificationBadgeThreeTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 3*5, repeats: false)
let notificationBadgeThreeRequest = UNNotificationRequest.init(identifier: "3", content: notificationBadgeThreeContent, trigger: notificationBadgeThreeTrigger)
center.add(notificationBadgeThreeRequest)
// Schedule badge value of 4 in 20 seconds
let notificationBadgeFourContent = UNMutableNotificationContent()
notificationBadgeFourContent.badge = NSNumber(value: 4)
let notificationBadgeFourTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 4*5, repeats: false)
let notificationBadgeFourRequest = UNNotificationRequest.init(identifier: "4", content: notificationBadgeFourContent, trigger: notificationBadgeFourTrigger)
center.add(notificationBadgeFourRequest)
// Schedule badge value of 0 in 25 seconds
let notificationBadgeZeroContent = UNMutableNotificationContent()
// MARK: Uncommenting this line setting title property will cause notification to fire properly.
//notificationBadgeZeroContent.title = "Zero!"
notificationBadgeZeroContent.badge = NSNumber(value: 0)
let notificationBadgeZeroTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 5*5, repeats: false)
let notificationBadgeZeroRequest = UNNotificationRequest.init(identifier: "0", content: notificationBadgeZeroContent, trigger: notificationBadgeZeroTrigger)
center.add(notificationBadgeZeroRequest)
}
@IBAction func listNotifications(_ sender: Any) {
let center = UNUserNotificationCenter.current()
center.getDeliveredNotifications() { (notificationArray) -> Void in
print("Delivered notifications: \(notificationArray)")
}
center.getPendingNotificationRequests() { (notificationArray) -> Void in
print("Pending notifications: \(notificationArray)")
}
}
// MARK: UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
print("Received notification: \(notification)")
completionHandler([.alert,.badge,.sound])
}
}
Well then I finally managed to make these two alerts functionning. As if posting this question on stackoverflow helped me to open my mind on that subject that hold me for the last few days (also these are really simple answers which is pretty shameful).
Here are my solutions if somebody come accross this post.
The Alert issue
For the alert that should show up when the app is closes, for instance when the app is killed by the user when in background, the code snippet is 'correct' overall. The point is that, when the appDelegate trigger the applicationWillTerminate: function, the system has already started to dealloc/dismantle the whole memory of your application. Therefore, if your app has many views loaded, and many datas to free, the thread that add the notification to the center has enough time to do it's task. But if the application has only few memory to dispose of, the notification is never added to the notifications center's queue.
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(@"applicationWillTerminate");
// Notification terminate
[Utils closePollenNotification];
// Pause the termination thread
[NSThread sleepForTimeInterval:0.1f];
}
So in my case I added a simple sleep in applicationWillTerminate right after creating the notification, which give enough time for it to be registered. (Note: I don't know if this is a good practice but it worked for me).
The Badge Issue
Obviously, after a better understanding of the Apple documentation, setting content.badge to 0 does not remove the previous badge set. It just tells the notification not to update the badge. To remove it, I simply had to call sharedApplication function :
//Reset badge icon
+(void) resetBadgeIcon {
NSLog(@"reset badge");
// remove basge
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
}
So simple.
Hope this can help somebody.