Search code examples
iosswiftusernotifications

Usernotification framework badge does not increase


I am using UserNotification framework in my app and sending local notifications (not push notifications), and I want to set the badge to the number of notifications received so what I did was to set the number of notifications received into a user default then I tried to assign the value to the badge to get me a badge number but the badge number would not increase. This is my code below

To set value of received notification

center.getDeliveredNotifications { notification in

    UserDefaults.standard.set(notification.count, forKey: Constants.NOTIFICATION_COUNT)
    print("notification.count \(notification.count)")
    print(".count noti \(UserDefaults.standard.integer(forKey: Constants.NOTIFICATION_COUNT))")

}

This accurately prints the number of notification received and when I decided to set it to my badge it only shows 1

content.badge = NSNumber(value: UserDefaults.standard.integer(forKey: Constants.NOTIFICATION_COUNT))

I have no idea why the value does not increase every time. Any help would be appreciated.

Or if it is possible to always update the badge anywhere in the app.


Solution

  • Send the local notifications like so:

    func sendNotification(title: String, subtitle: String, body: String, timeInterval: TimeInterval) {
        let center = UNUserNotificationCenter.current()
        center.getPendingNotificationRequests(completionHandler: { pendingNotificationRequests in
    
            //Use the main thread since we want to access UIApplication.shared.applicationIconBadgeNumber
            DispatchQueue.main.sync {
    
                //Create the new content
                let content = UNMutableNotificationContent()
                content.title = title
                content.subtitle = subtitle
                content.body = body
    
                //Let's store the firing date of this notification in content.userInfo
                let firingDate = Date().timeIntervalSince1970 + timeInterval
                content.userInfo = ["timeInterval": firingDate]
    
                //get the count of pending notification that will be fired earlier than this one
                let earlierNotificationsCount: Int = pendingNotificationRequests.filter { request in
    
                    let userInfo = request.content.userInfo
                    if let time = userInfo["timeInterval"] as? Double {
                        if time < firingDate {
                            return true
                        } else {
    
                            //Here we update the notofication that have been created earlier, BUT have a later firing date
                            let newContent: UNMutableNotificationContent = request.content.mutableCopy() as! UNMutableNotificationContent
                            newContent.badge = (Int(truncating: request.content.badge ?? 0) + 1) as NSNumber
                            let newRequest: UNNotificationRequest =
                                UNNotificationRequest(identifier: request.identifier,
                                                      content: newContent,
                                                      trigger: request.trigger)
                            center.add(newRequest, withCompletionHandler: { (error) in
                                // Handle error
                            })
                            return false
                        }
                    }
                    return false
                }.count
    
                //Set the badge
                content.badge =  NSNumber(integerLiteral: UIApplication.shared.applicationIconBadgeNumber + earlierNotificationsCount + 1)
                let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval,
                                                                repeats: false)
    
                let requestIdentifier = UUID().uuidString  //You probably want to save these request identifiers if you want to remove the corresponding notifications later
                let request = UNNotificationRequest(identifier: requestIdentifier,
                                                    content: content, trigger: trigger)
    
                center.add(request, withCompletionHandler: { (error) in
                    // Handle error
                })
            }
        })
    }
    

    (You may need to save the requests' identifiers (either in user defaults or core data if you'd like to update them, or even cancel them via removePendingNotificationRequests(withIdentifiers:))

    You can call the above function like so:

    sendNotification(title: "Meeting Reminder",
                     subtitle: "Staff Meeting in 20 minutes",
                     body: "Don't forget to bring coffee.",
                     timeInterval: 10)
    

    Declare your view controller as a UNUserNotificationCenterDelegate:

    class ViewController: UIViewController, UNUserNotificationCenterDelegate {
        override func viewDidLoad() {
            super.viewDidLoad()
            UNUserNotificationCenter.current().delegate = self
        }
        //...
    }
    

    And to handle interacting with the notification, update the badge of the app, and the badge of the upcoming notifications:

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    
        //UI updates are done in the main thread
        DispatchQueue.main.async {
            UIApplication.shared.applicationIconBadgeNumber -= 1
        }
    
        let center = UNUserNotificationCenter.current()
        center.getPendingNotificationRequests(completionHandler: {requests in
            //Update only the notifications that have userInfo["timeInterval"] set
            let newRequests: [UNNotificationRequest] =
                requests
                    .filter{ rq in
                        return rq.content.userInfo["timeInterval"] is Double?
                    }
                    .map { request in
                        let newContent: UNMutableNotificationContent = request.content.mutableCopy() as! UNMutableNotificationContent
                        newContent.badge = (Int(truncating: request.content.badge ?? 0) - 1) as NSNumber
                        let newRequest: UNNotificationRequest =
                            UNNotificationRequest(identifier: request.identifier,
                                                  content: newContent,
                                                  trigger: request.trigger)
                        return newRequest
            }
            newRequests.forEach { center.add($0, withCompletionHandler: { (error) in
                // Handle error
            })
            }
        })
        completionHandler()
    }
    

    This updates the app badge by decreasing it when a notification is interacted with ie tapped. Plus it updates the content badge of the pending notifications. Adding a notification request with the same identifier just updates the pending notification.

    To receive notifications in the foreground, and increase the app badge icon if the notification is not interacted with, implement this:

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        DispatchQueue.main.async {
            UIApplication.shared.applicationIconBadgeNumber += 1
        }
        completionHandler([.alert, .sound])
    }
    

    Here are some gifs:

    • 1st: Receiving local notifications increases the app badge. Whereas interacting with a notification decreases the app badge.

    • 2nd: Receiving local notifications when the app is killed (I used a trigger timeInterval of 15s in this).

    • 3rd: Receiving notification whilst in the foreground increases the app badge unless the user interacts with it.

    The complete class used in my test project looks like this:

    import UIKit
    import UserNotifications
    
    class ViewController: UIViewController, UNUserNotificationCenterDelegate {
        var bit = true
        @IBAction func send(_ sender: UIButton) {
            let time: TimeInterval = bit ? 8 : 4
            bit.toggle()
            sendNotification(title: "Meeting Reminder",
                             subtitle: "Staff Meeting in 20 minutes",
                             body: "Don't forget to bring coffee.",
                             timeInterval: time)
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
    
            UNUserNotificationCenter.current().delegate = self
        }
    
        func sendNotification(title: String, subtitle: String, body: String, timeInterval: TimeInterval) {
            let center = UNUserNotificationCenter.current()
            center.getPendingNotificationRequests(completionHandler: { pendingNotificationRequests in
                DispatchQueue.main.sync {
                    let content = UNMutableNotificationContent()
                    content.title = title
                    content.subtitle = subtitle
                    content.body = body
                    let firingDate = Date().timeIntervalSince1970 + timeInterval
                    content.userInfo = ["timeInterval": firingDate]
                    let earlierNotificationsCount: Int = pendingNotificationRequests.filter { request in
                        let userInfo = request.content.userInfo
                        if let time = userInfo["timeInterval"] as? Double {
                            if time < firingDate {
                                return true
                            } else {
                                let newContent: UNMutableNotificationContent = request.content.mutableCopy() as! UNMutableNotificationContent
                                newContent.badge = (Int(truncating: request.content.badge ?? 0) + 1) as NSNumber
                                let newRequest: UNNotificationRequest =
                                    UNNotificationRequest(identifier: request.identifier,
                                                          content: newContent,
                                                          trigger: request.trigger)
                                center.add(newRequest, withCompletionHandler: { (error) in
                                    // Handle error
                                })
                                return false
                            }
                        }
                        return false
                        }.count
                    content.badge =  NSNumber(integerLiteral: UIApplication.shared.applicationIconBadgeNumber + earlierNotificationsCount + 1)
                    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval,
                                                                    repeats: false)
    
                    let requestIdentifier = UUID().uuidString  //You probably want to save these request identifiers if you want to remove the corresponding notifications later
                    let request = UNNotificationRequest(identifier: requestIdentifier,
                                                        content: content, trigger: trigger)
    
                    center.add(request, withCompletionHandler: { (error) in
                        // Handle error
                    })
                }
            })
        }
    
        func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
            DispatchQueue.main.async {
                UIApplication.shared.applicationIconBadgeNumber += 1
            }
            completionHandler([.alert, .sound])
        }
    
        func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
            DispatchQueue.main.async {
                UIApplication.shared.applicationIconBadgeNumber -= 1
            }
    
            let center = UNUserNotificationCenter.current()
            center.getPendingNotificationRequests(completionHandler: {requests in
                let newRequests: [UNNotificationRequest] =
                    requests
                        .filter{ rq in
                            return rq.content.userInfo["timeInterval"] is Double? 
                        }
                        .map { request in
                            let newContent: UNMutableNotificationContent = request.content.mutableCopy() as! UNMutableNotificationContent
                            newContent.badge = (Int(truncating: request.content.badge ?? 0) - 1) as NSNumber
                            let newRequest: UNNotificationRequest =
                                UNNotificationRequest(identifier: request.identifier,
                                                  content: newContent,
                                                  trigger: request.trigger)
                            return newRequest
                }
                newRequests.forEach { center.add($0, withCompletionHandler: { (error) in
                    // Handle error
                })
                }
            })
            completionHandler()
        }
    }