Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 777
King Avatar asked Sep 14 '18 01:09

King


People also ask

How iOS push notification works?

Once you've completed setting up push notifications on your providers and in your app, your providers can send notification requests to APNs. APNs then delivers the notification to each targeted device. Once APNs receives a notification, the system delivers the notification to the appropriate app on the device.

What is Apple push notification used for?

Apple Push Notification service (commonly referred to as Apple Notification Service or APNs) is a platform notification service created by Apple Inc. that enables third party application developers to send notification data to applications installed on Apple devices.

What is a user notification?

A notification is a message that Android displays outside your app's UI to provide the user with reminders, communication from other people, or other timely information from your app. Users can tap the notification to open your app or take an action directly from the notification.

Are Apple push notifications guaranteed?

The system makes every attempt to deliver local and remote notifications in a timely manner, but delivery isn't guaranteed. The PushKit framework offers a more timely delivery mechanism for specific types of notifications, such as those VoIP and watchOS complications use.


2 Answers

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()
    }
}
like image 196
ielyamani Avatar answered Oct 19 '22 04:10

ielyamani


I'm assuming this all a local notification.

AFAIK there is solution to your question!

When the notification arrives, you're either in foreground or background.

  • foreground: you get the userNotificationCenter(_:willPresent:withCompletionHandler:) callback but I don't think in that case you'll want to increase the badge right? Because the user has just seen it. Though I can imagine where you might need to do such. Suppose your app is like WhatsApp and the user has the app opened and is sending a message to his mother. Then a message from his father arrives. At this point he hasn't opened the messages between him and his father yet he sees the notification. In your willPresent you could query the getDeliveredNotifications and and adjust your badge count.
  • background: for iOS10+ version for local notifications you're out of luck! Because there is NO callback for you. The notification gets delivered to the OS and that's it! There use to be a named application:didReceiveLocalNotification: but that's deprecated. For more on that see here
  • When user taps (foreground or backend) then you'll get the userNotificationCenter(_:didReceive:withCompletionHandler:) but that has no use again because the user has already acknowledged receiving the notification and increasing the badge in this case doesn't make sense.

Long story short AFAIK there is nothing you can do for local notifications.

If it's a remote notification then in the application(_:didReceiveRemoteNotification:fetchCompletionHandler:) you can query the delivered notifications and increase the badge count...

EDIT:

Since the badgeCount is attached to the arriving notification, then if you can update its badgeCount prior to arrival then you're all good. e.g. at 12pm you can always query the list of pendingNotifications. It will give you all the notifications arriving after 12pm and update the badgeCount on them if necessary e.g. decrease their badgeCount if some delivered notifications are read. For a complete solution on this see see Carspen90's answer. The gist of his answer is

for any new notification you want to send:

  1. get the pendingNotifications
  2. filter notifications which their firingDate is sooner than the new to be sent notification and get its count
  3. set the new notification's badge to app's badgeCount + filteredCount + 1
  4. if any of the pending notifications have a firingDate greater than the new notification we just added then we will increase the badgeCount on the pending notification by 1.
  5. obviously again whenever you interact with delivered notifications, then you have to get all pendingNotifications again and decrease their badgeCount by 1

CAVEAT:

You can't do such for notifications which their trigger is based on location because obviously they don't care about time.

like image 45
mfaani Avatar answered Oct 19 '22 04:10

mfaani