IOS 10.2 UserNotifications problems on simple Alert and Badge - ios

Before writing this post I made a lot of researches on UserNotification Framework, that substituted UILocalNotification in IOS 10. I also followed this tutorial to learn everything about this new feature : http://useyourloaf.com/blog/local-notifications-with-ios-10/.
Today I'm encountering so much troubles to implement such trivial Notifications and since it's a recent new feature I couldn't find any solutions (especially in objective C)! I currently have 2 different notifications, one Alert and one Badge updtate.
The Alert Issue
Before Updating my Phone from IOS 10.1 to 10.2, I made an alert on the Appdelegate that is triggered immediatly whenever the user closes the app manually:
-(void)applicationWillTerminate:(UIApplication *)application {
NSLog(#"applicationWillTerminate");
// Notification terminate
[self registerTerminateNotification];
}
// Notification Background terminate
-(void) registerTerminateNotification {
// the center
UNUserNotificationCenter * notifCenter = [UNUserNotificationCenter currentNotificationCenter];
// Content
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
content.title = #"Stop";
content.body = #"Application closed";
content.sound = [UNNotificationSound defaultSound];
// Trigger
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
// Identifier
NSString *identifier = #"LocalNotificationTerminate";
// création de la requête
UNNotificationRequest *terminateRequest = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
// Ajout de la requête au center
[notifCenter addNotificationRequest:terminateRequest withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Error %#: %#",identifier,error);
}
}];
}
Before IOS 10.2 it worked just fine, when I closed the app manually, an alert showed up. But since I updated to IOS 10.2, nothing shows up without any reason, I havent change anything, and I can't see what's missing..
The Badge Issue
I also tried (only in IOS 10.2 this time) to implement badging on my app icon which worked just fine, until I tried to remove it. Here is the function that does it :
+(void) incrementBadgeIcon {
// only increment if application is in background
if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground){
NSLog(#"increment badge");
// notif center
UNUserNotificationCenter *notifCenter = [UNUserNotificationCenter currentNotificationCenter];
// Content
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
content.badge = [NSNumber numberWithInt:1];
// Trigger
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
// Identifier
NSString *identifier = #"LocalNotificationIncrementBadge";
// request
UNNotificationRequest *incrementBadgeRequest = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
// Ajout de la requête au center
[notifCenter addNotificationRequest:incrementBadgeRequest withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Error %#: %#",identifier,error);
}
}];
}
}
For now it does not increment the badge number, as it's name should suggest, but it just set the badge number to 1. Documentation says that if you set content.badge to 0, it removes it, but this does not work. I tried with other numbers, when I manually change it to '2', '3', etc... it changes, but if I set it to 0, it does not work.
Also, in the tutorial I linked earlier, it is mentionned several functions as getPendingNotificationRequests:completionHandler: and getDeliveredNotificationRequests:completionHandler:. I noticed that, when I call these functions right after calling incrementBadgeIcon, if the content.badge is set to '1', '2' etc... it appears in the pending notifications list. However, when I set it to 0, it does not appear anywhere. I get no error, no warning in Xcode, and my application badge still remain.
Does anyone know how I can fix these two Alerts?
Thank in advance
PS: I also tried to use removeAllPendingNotificationRequests and removeAllDeliveredNotifications for both without success.

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.

This fix only works on older versions of iOS. Use only for backward compatibility.
This is a fix for the badge issue OP describes (but not the alert issue).
Step 1: Send the notification with badge = negative 1
On your UNMutableNotificationContent set content.badge = #-1 instead of 0. This has 2 benefits:
unlike 0, -1 actually clears the badge (if you do step 2 as well)
unlike [UIApplication sharedApplication].applicationIconBadgeNumber = 0 it won't clear alerts from your app in notification center.
Step 2: Implement the UNUserNotificationCenterDelegate
You'll need to implement the UNUserNotificationCenterDelegate and do the following:
Implement the willPresentNotification method in your delegate, and call completionHandler(UNNotificationPresentationOptionBadge) in the body. Note: I check the request id, and call the default UNNotificationPresentationOptionNone unless this is specifically a notifications designed to clear the badge.
Don't forget to retain your delegate instance somewhere with a strong pointer. The notification center does not keep a strong pointer.
After these 2 changes, you can again clear the badge with a local notification.

Related

iOS local notification and calendar event does not appear while app is not running

We'd like to implement silent push in our project, but facing an odd problem!
First, there's a demo project and works fine in my program environment,
but after I apply the same code to my other project, the local notification does not appear after received silent push notification in AppDelegate (something magic) while the app is disconnect with xcode/in background, and there's plan B about calendar, but same as above, therefore, I have no idea to solve, any suggestion?
1. silent push notification & local notification demo project
* xcode 10.1, deployment target iOS 10.0
* objective-c
* reference tutorial: https://medium.com/#mikru168/ios-%E6%9C%AC%E5%9C%B0%E9%80%9A%E7%9F%A5-local-notification-b25229f279ec
-------> works charme. Local notification appears after receive silent push notification.
2. my project + "1."(above code)
* xcode 10.1, iOS 9.0
* objective-c mix swift 4.0
-------> works fine while debug(connect with xcode), both of while app in foreground and background.
-------> (disconnect with xcode) local notification does not appear while app in background or terminated, but works fine in foreground.
3. my project + calendar event
-------> works fine while debug(connect with xcode), both of while app in foreground and background.
-------> (disconnect with xcode) calendar notification does not appear while app in background or terminated, but works fine in foreground.
I tried with your given demo and I just added some new methods and code.
It's working for me when an app is disconnected from Xcode.
Please try to add the following methods to your project and test it.
First of all, please check following services are enabled in your project
Background Modes > Remote Notification
Push Notifiation
Second, i added this methods in didFinishLaunchingWithOptions
application.registerForRemoteNotifications()
Also, Added following methods in the appdelegate class
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
{
self.addLocalNotification()
completionHandler(.newData)
}
func addLocalNotification(){
let content = UNMutableNotificationContent()
content.title = "title:Test"
content.subtitle = "subtitle:No---"
content.body = "body:Silent Notification"
content.badge = 1
content.sound = UNNotificationSound.default()
// 設置通知的圖片
let imageURL = Bundle.main.url(forResource: "apple", withExtension: "png")
let attachment = try! UNNotificationAttachment(identifier: "", url: imageURL!, options: nil)
content.attachments = [attachment]
// 設置點擊通知後取得的資訊
content.userInfo = ["link" : "https://www.facebook.com/franksIosApp/"]
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: "notification", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: {error in
print("成功建立通知...")
})
}
I would like to share with someone who's stuck this like me,
here's Apple's troubleshooting, and it's really helpful:
https://developer.apple.com/library/archive/technotes/tn2265/_index.html
my finally solution is the trigger time beyond 60s.

Schedule and handle local notifications in iOS 10

I've been working on one of my project where I allow users to schedule multiple notifications at their desired time.
iOS 10 gave us the ability to use a DateComponents as the fire date for our notification but I'm kind of lost as to how I'm supposed to schedule and handle multiple notifications.
Each notification needs its own request which then in turn needs its own identifier else you cannot create multiple notifications.
I figured I had to work with the identifier to schedule and handle notifications, but now my code is such a mess that I've been seriously asking myself if there hasn't been an easier way of doing this.
According to my data model the notification unique identifier is composed of :
The id of my model
A number that increments each time a new notification is created
The above is separated by an underscore
So for example if I had to schedule 10 notifications for the object with the id 3 it would look like this : 3_1, 3_2, 3_3...
Every time I receive a notification I loop trough the received notifications to update my UI. And when the user desires to delete the received notifications for a specific model, I loop through the received notification's unique identifiers by checking the identifiers that starts with the same ID.
I don't really see how I could manage to do it otherwise because according to the documentation, the only way of deleting a delivered notification is by using the identifier : UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers:)
The thing is, it's creating all sort of problems and while I could easily correct them it looks very hacky. I'm not really proud of what I've done and I am looking for more clever ways of getting around it. I intentionally did not post any code because that's not the problem. The approach is the real problem.
I'm asking here because the UserNotifications framework is pretty new and I haven't been able to find ressources taking about this subject.
Any idea ? Thanks in advance.
Edit : Here's some code.
#available(iOS 10.0, *)
func checkDeliveredAndPendingNotifications(completionHandler: #escaping (_ identifierDictionary: Dictionary<String, Int>) -> ()) {
var identifierDictionary:[String: Int] = [:]
UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in
for notification in notifications {
let identifierArraySplit = notification.request.identifier.components(separatedBy: "_")
if identifierDictionary[identifierArraySplit[0]] == nil || identifierDictionary[identifierArraySplit[0]]! < Int(identifierArraySplit[1])! {
identifierDictionary[identifierArraySplit[0]] = Int(identifierArraySplit[1])
}
}
UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { (requests) in
for request in requests {
let identifierArraySplit = request.identifier.components(separatedBy: "_")
if identifierDictionary[identifierArraySplit[0]] == nil || Int(identifierArraySplit[1])! > identifierDictionary[identifierArraySplit[0]]! {
identifierDictionary[identifierArraySplit[0]] = Int(identifierArraySplit[1])
}
}
completionHandler(identifierDictionary)
})
}
}
#available(iOS 10.0, *)
func generateNotifications() {
for medecine in medecines {
self.checkDeliveredAndPendingNotifications(completionHandler: { (identifierDictionary) in
DispatchQueue.main.async {
self.createNotification(medecineName: medecine.name, medecineId: medecine.id, identifierDictionary: identifierDictionary)
}
})
}
}
#available(iOS 10.0, *)
func createNotification(medecineName: String, medecineId: Int identifierDictionary: Dictionary<String, Int>) {
let takeMedecineAction = UNNotificationAction(identifier: "TAKE", title: "Take your medecine", options: [.destructive])
let category = UNNotificationCategory(identifier: "message", actions: [takeMedecineAction], intentIdentifiers:[], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
let takeMedecineContent = UNMutableNotificationContent()
takeMedecineContent.userInfo = ["id": medecineId]
takeMedecineContent.categoryIdentifier = "message"
takeMedecineContent.title = medecineName
takeMedecineContent.body = "It's time for your medecine"
takeMedecineContent.badge = 1
takeMedecineContent.sound = UNNotificationSound.default()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false)
var takeMedecineIdentifier = ""
for identifier in identifierDictionary {
if Int(identifier.key) == medecineId {
let nextIdentifierValue = identifier.value + 1
takeMedecineIdentifier = String(medecineId) + "_" + String(nextIdentifierValue)
}
}
let takeMedecineRequest = UNNotificationRequest(identifier: takeMedecineIdentifier, content: takeMedecineContent, trigger: trigger)
UNUserNotificationCenter.current().add(takeMedecineRequest, withCompletionHandler: { (error) in
if let _ = error {
print("There was an error : \(error)")
}
})
}
In the checkDeliveredAndPendingNotifications method, I loop through all the pending and delivered notifications, so later on I can create an identifier that does not exist already. I haven't found another way to generate unique identifiers for each notification.
As most of the work is done on another async queue, I've also created a completion handler when he has finished its job. I then call the createNotification method on the main thread (because I'm using Realm, and I'm obliged too do this) which should create a notification.
The problem here is the func add(UNNotificationRequest, withCompletionHandler: (Error?) -> Void)? = nil) method which is also doing work asynchronously. So when I go back to my loop in generateNotifications the checkDeliveredAndPendingNotifications return incorrect data. Well not incorrect it's just that the notification hasn't been created yet...
I'm a total noob with threading and I'm stuck with these kind of operations and I don't know where to go. I'm not sure I'm approaching the problem the right way.
You can get all notification and set/Delete as you need. Have a look on this accepted answer for both Objc c and swift.
How to schedule a local notification in iOS 10 (objective-c)

How to do badge increment on iOS push notification

I'm currently getting badge from payload. But how can i update that value.
{"aps":
{"alert":"Notification Hub test notification2",
"badge":1,
"sound":"Default"}
}
when i send this it is always shows 1 in the badge number but when i go inside app and exit it's getting updated.It is because of this,
func applicationDidBecomeActive(application: UIApplication) {
UIApplication.sharedApplication().applicationIconBadgeNumber = UIApplication.sharedApplication().applicationIconBadgeNumber + 1
}
But when i send a new push notification it's again shows 1 as badge number in the icon.
How should i do this, to my understand i should send updated badge when i send the payload. For that i have to deal with backend.
Is there any suggestion other than this to handle inside app?
If your project has a notification extension, you can do a badge setting inside it.
First of all, enable App Groups and save your badge count from the app notification service extension in UserDefaults. We are using app groups because we need to clear the badge count from AppDelegate when opening the app.
Also you can set value of badge as bestAttemptContent.badge from notification service extension
In notification service extension:
if let userDefaults = UserDefaults(suiteName: "group.com.bundle.appname") {
let badgeCount = userDefaults.integer(forKey: "badgeCount")
if badgeCount > 0 {
userDefaults.set(badgeCount + 1, forKey: "badgeCount")
bestAttemptContent.badge = badgeCount + 1 as NSNumber
} else {
userDefaults.set(1, forKey: "badgeCount")
bestAttemptContent.badge = 1
}
}
In AppDelegate you can clear badge...and set value for the badge as zero on userdefaults
if let userDefaults = UserDefaults(suiteName: "group.com.bundle.appname") {
userDefaults.set(0, forKey:"badgeCount") // "change "badgecount" to be "badgeCount"
}
UIApplication.shared.applicationIconBadgeNumber = 0
I will give my way to you :
My plan is to use KVO to listen the [UIApplication sharedApplication].applicationIconBadgeNumber
- (void)setupBadgeOperation {
[[UIApplication sharedApplication] addObserver:self forKeyPath:#"applicationIconBadgeNumber" options:NSKeyValueObservingOptionNew context:nil];
}
And once value changed, I use [[NSNotificationCenter defaultCenter] postNotificationName:STATUS_BADGENUMBER_CHANGED object:nil] to inform the UI where needs to be modified for the change of badge.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context {
if ([keyPath isEqualToString:#"applicationIconBadgeNumber"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:STATUS_BADGENUMBER_CHANGED object:nil];
}
}
There is three case of receiving a remote notification to change the [UIApplication sharedApplication].applicationIconBadgeNumber.
a. app is foreground
b. app is background
c. app is not launch
in a and b case, this method will be called:
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
void(^tapBlock)(void(^completeHandler)()) = ^(void(^completeHandler)()) {
// here I remove some code not related with the question
NSNumber *badgeNumber = userInfo[#"aps"][#"badge"];
[UIApplication sharedApplication].applicationIconBadgeNumber = badgeNumber.integerValue;
}
in c case, there is no way to get the callback, so I will do it manually.
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[NSNotificationCenter defaultCenter] postNotificationName:STATUS_BADGENUMBER_CHANGED object:nil];
}
That's all, and works well for me.
Here my way Swift:
1) You need to set badge ( a variable you can call it whatever you want) to zero into Firebase Realtime database once you launch the app. Plus set application.applicationIconBadgeNumber = 0. Here is how I do it in AppDelegate:
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
application.applicationIconBadgeNumber = 0
// Reset badge number to zero
let userID = Auth.auth().currentUser?.uid
let dbRef = Database.database().reference()
dbRef.child("Users Info").child(userID!).updateChildValues(["badge":"0"], withCompletionBlock: { (err, ref) in
if err != nil {
print(err!)
return
}
})
}
2) Go to index.js where a function is triggered.
...
return admin.database().ref('/Users Info/' + owner).once('value', snapshot => {
var ownerValue = snapshot.val();
var badgeNumber = parseInt(ownerValue.badge) + 1;
// Notification details.
const payload = {
notification: {
title: 'You have a new request!',
body: `A new request from ${downedBy.name}.`,
badge:`${badgeNumber}`,
sound: 'default',
}
};
...
...
// Upload the new value of badge into Firebase to keep track of the number
return admin.database().ref('/Users Info/' + owner).update({badge:badgeNumber});
...
})
First of all, leave it to your server guys to send you updated badge counter. Your job is only to display it, not to maintain it on your end.
You need to perform the update operations in applicationDidReceiveRemoteNotifications Delegate method like this:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]){
let notification = userInfo["aps"] as? NSDictionary
let badge = (notification["badge"] as String).toInt()
UIApplication.sharedApplication().applicationIconBadgeNumber = UIApplication.sharedApplication().applicationIconBadgeNumber + badge;
}
And in your DidBecomeActive method:
func applicationDidBecomeActive(application: UIApplication) {
if(UIApplication.sharedApplication().applicationIconBadgeNumber!=0){
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}
This is with the assumption that your server sends you updated push badge counter. In parse.com, I'd do something like this to convey to the Server that push counter must be set to zero now:
var currentInstallation = PFInstallation.currentInstallation()
if currentInstallation.badge != 0 {
currentInstallation.badge = 0
currentInstallation.save
}
If I don't do this, Server will assume that Push has never been read yet and will increment the badge counter on their end on the next push and so on.
Keep your badge count in NSUserDefault for maximum availability. even if you restart your device, then also you would be able to get badge count from NSUserDefault.
When you get another payload then you could keep incrementing badge count in NSUserDefault.
Server must increment much count in #"badge" key. When you open app, you need send request to server - Reset Push Counter (badge). In applicationDidBecomeActive method.
{"aps":
{"alert":"Notification Hub test notification2",
"badge":20,
"sound":"Default"}
}
Whenver we use the push notification.
we have to update its badge count. Otherwise each and every time badge will be same.
e.g. If you got a notification its count is 1 and you read it it will still shows badge 1. so for this you have to clear the badge.
by using code like this,
func applicationDidBecomeActive(application: UIApplication) {
application.applicationIconBadgeNumber = 0
}
For handling the badge best solution is use the notification extension you can create a. notification extension and also use KVO to update the badge in app.
and app group for saving the number of badge and managing in app.
when you get notification you can read how many notification you have and add the badge of notification to that and update your notification that its
guard let content = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
contentHandler(request.content)
return
}
if let badge = bestAttemptContent?.badge as? Int {
let newBadge = badge + NotificationManangerBridge.shared.getTotalBadge()
content.badge = NSNumber(value: newBadge)
NotificationManangerBridge.shared.updateTotalBadge(badgeCount: newBadge)
} else {
content.badge = 0
}
guard let notification = bestAttemptContent?.copy() as? UNNotificationContent else { return }
contentHandler(notification)
You are going in a correct direction. You will have to update badge number from your payload and that's the standard way in push notifications.

How do I properly use cancelLocalNotification?

I believe I am using cancelLocalNotification improperly.
I have a recurring notification that runs conditionally, which was created using the following code:
let localNotification: UILocalNotification = UILocalNotification()
localNotification.alertAction = "Inactive Membership"
localNotification.alertBody = "Our system has detected that your membership is inactive..."
localNotification.fireDate = NSDate(timeIntervalSinceNow: 5)
localNotification.repeatInterval = .Minute
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
This notification successfully runs every single minute (for testing purposes). Obviously, I'd like a way to conditionally remove these notifications, so I've tried to use cancelLocalNotification to do so.
How I think cancelLocalNotification works
My intuition is that cancelLocalNotification will remove all the notifications for that specific notification object. Here's how I'm using it.
UIApplication.sharedApplication().cancelLocalNotification(localNotification)
What actually happens
I've stepped through my function and verified that the cancelLocalNotification code does get called. However, I keep getting my notification every minute.
My Question
How do I properly cancel a UILocalNotification that has been scheduled?
Full code:
static func evaluateMemberStatusNotifications() {
let userDefaults = Global.app.userDefaults
let localNotification: UILocalNotification = UILocalNotification()
print("evaluating profile notifications")
// is the user active? if so no notification
let isActive : Bool = userDefaults.valueForKey("ActiveMember") as! Bool // false == inactive
print("is the user active?")
if !isActive {
print("user is not active, checking if notification code has run")
// if userDefaults is nil for this value, we'll set it to false
if (userDefaults.valueForKey("ProfileNotificationHasRun") == nil) {
print("nil! setting ProfileNotificationHasRun to 'false'")
userDefaults.setValue(false, forKey: "ProfileNotificationHasRun")
}
let statusNotification = userDefaults.valueForKey("ProfileNotificationHasRun") as! Bool
// has this code been run? If not run it
if !statusNotification {
print("running notification code")
// we schedule a notification
localNotification.alertAction = "Inactive Membership"
localNotification.alertBody = "Our system has detected that your membership is inactive."
localNotification.fireDate = NSDate(timeIntervalSinceNow: 5)
localNotification.category = "status"
localNotification.repeatInterval = .Day
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
userDefaults.setValue(true, forKey: "ProfileNotificationHasRun")
} else {
print("notification code has already run, time interval has been set")
}
} else {
print("member is active, remove Inactive notification")
// if the member is active, we remove the notification so the user doesn't
// keep getting notified
UIApplication.sharedApplication().cancelLocalNotification(localNotification)
userDefaults.setValue(false, forKey: "ProfileNotificationHasRun")
}
}
You're creating a new UILocalNotification (line 3 of your gist) and then cancelling it. That new notification was never scheduled. You need to get the existing, scheduled notification object and cancel that.
You can access your existing, scheduled notifications through the UIApplication.sharedApplication().scheduledLocalNotifications array.
Or you could just cancel all scheduled local notifications by calling UIApplication.sharedApplication().cancelAllLocalNotifications().
UPDATE
Each call to evaluateMemberStatusNotifications creates a new instance of UILocalNotification, then (depending on the ActiveMember value) either configures and schedules that new notification, or tries to delete the new (unscheduled) notification.
Instead, you should just create a new notification in the !isActive branch where you want to schedule it.
In the other branch, you need to find the existing notification in the scheduledLocalNotifications array and (if you find it) cancel that existing notification.
Since you say you have other notifications that you don't want to mess with, you should use the userInfo property of the notification to identify it. For example, when configuring the notification before scheduling it:
localNotification.alertAction = "Inactive Membership"
localNotification.alertBody = "Our system has detected that your membership is inactive. You may continue using this application though some features may not be available to you until your membership is reinstated."
localNotification.fireDate = NSDate(timeIntervalSinceNow: 5)
localNotification.category = "status"
localNotification.repeatInterval = .Day
// *** NEW
localNotification.userInfo = [ "cause": "inactiveMembership"]
And when you want to cancel the existing notification:
let maybeNotification = UIApplication.sharedApplication().scheduledLocalNotifications?.filter({ (notification: UILocalNotification) -> Bool in
notification.userInfo?["cause"] as? String == "inactiveMembership"
}).first
if let notification = maybeNotification {
UIApplication.sharedApplication().cancelLocalNotification(notification)
}

Set timeout for UILocalNotification (remove it from lockscreen and notification center after some time)

I would like to set a UILocalNotification that will disappear from the lock screen and the notification center after five minutes (if the user won't tap on it).
Can I set a timeout for the notification? Or maybe fire another notification that will delete it?
I believe Facebook does this by sending a silent push notification from their servers that triggers this code in the app:
[[UIApplication sharedApplication] setApplicationIconBadgeNumber: 0];
[[UIApplication sharedApplication] cancelAllLocalNotifications];
To keep the solution entirely local to the device, you can set an NSTimer that can then trigger the above code at the proper interval. This comes with the huge caveat that if your app is suspended when in the background your timer won't fire until it is brought back an active state.
Disclaimer: The code will work only if you wake the app. In my case this code is called from application(_:handleActionWithIdentifier:for:completionHandler:), which is triggered from a local notification, which in its turn is called from a bluetooth beacon monitoring. So that entering the bluetooth beacon's range wakes the app for me. What will be waking the app your case is up to you - this can be a silent notification form your backend, for example.
Also I doubt that iOS would let the timer to run for 5 mins, in my case the 20 seconds timer fires with very high probability (though I understand that it is not 100% guaranteed and sometimes notification might remain, however it is not crucial in my case).
Closing the notification in 20 seconds:
if #available(iOS 10.0, *) {
... <setting up an iOS 10 notification content and trigger>
notificationCenter.add(UNNotificationRequest(identifier: "myidentifier",
content: content,
trigger: trigger))
Timer.scheduledTimer(timeInterval: 20.0,
target: self,
selector: #selector(self.cancelDeliveredNotification),
userInfo: nil,
repeats: false)
} else {
let notification = UILocalNotification()
... <setting up an iOS8/9 notification>
Timer.scheduledTimer(timeInterval: 20.0,
target: self,
selector: #selector(self.cancelDeliveredNotification),
userInfo: notification as Any?,
repeats: false)
the cancelling function:
func cancelDeliveredNotification(_ sender: Timer) {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ["myidentifier"])
} else {
if let notificationToCancel = sender.userInfo as? UILocalNotification {
UIApplication.shared.cancelLocalNotification(notificationToCancel)
}
}
alternatively you can also do UNUserNotificationCenter.current().removeAllDeliveredNotifications()

Resources