How to increase notification badge number dynamically when local notification is delivered? - ios

My App sets up multiple notifications via a loop based on a calendar date and time. While everything else is working correctly with this, I cannot seem to get the badge number to increment properly. I have tried using a counter and setting
content.badge = self.counter as NSNumber
... or using ...
content.badge = NSNumber(value: UIApplication.shared.applicationIconBadgeNumber + 1)
but what happens is something like the following:
User gets notification 1,2,3,4,5 and badge number gets set to 5 content.badge = self.counter
User then "clears" notifications 2 and 3 -> and badge number is now 3 via my code (UIApplication.shared.applicationIconBadgeNumber - 1)
When the next notification (number 6) pops up, the badge number SHOULD show 4. It doesn't. It shows 6 because that's what was set via content.badge
What is the proper way to increment the badge number? I've looked at various posts here but they're for push notifications. Mine is for a local notifications. Can I set/update badge number WHEN a notification is actually delivered? I thought about trying this in...
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .badge, .sound])
}
...but that seems to only be for in-app notifications. Is there another function that I can do this in?

Related

How do I make a UNUserNotification that's consistent through app states?

I make a UNUserNotification like this:
let content = UNMutableNotificationContent()
content.title = "a title"
content.body = "a body"
content.sound = .default
content.categoryIdentifier = "\(type(of: self))"
let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: UNTimeIntervalNotificationTrigger(timeInterval: 0.01, repeats: false)
)
let category = UNNotificationCategory(
identifier: content.categoryIdentifier,
actions: [
UNNotificationAction(identifier: Strings.accept.id, title: Strings.accept.title, options: [.authenticationRequired, .foreground]),
UNNotificationAction(identifier: Strings.reject.id, title: Strings.reject.title, options: [.authenticationRequired, .destructive])
],
intentIdentifiers: [],
options: .customDismissAction
)
It will show a notification with a title and body, and two custom action buttons (accept and reject, it's an incoming call). It plays a sound too, but for some reason not if the app is foregrounded.
I also try to fake a persistent notification with it, like the way Whatsapp does it by repeating a local notification every ~4 seconds, but the second time it shows the notification the sound is cut off a bit. It's not the sound of the first one, the second one shows with its own sound playing but this time it doesn't fully play out. The sound is only ~1-2 seconds.
I don't understand why there are these differences, how do I make it always play a sound (foreground, background, not running) and how do I make sure the sound is not cut off the second time the notification is shown?
When notifications are received whilst the app is running the notifications are normally silenced so that the app can handle them in a more native way.
If I remember rightly, receiving the notification will call:
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
#escaping (UNNotificationPresentationOptions) -> Void) {
if someCondition {
completionHandler(.alert)
} else {
completionHandler(.init(rawValue: 0))
}
}
This method is called when the notification is received and if the app is running in the foreground, it will normally be silenced. The completion handler determines what action should be taken and whether or not a notification should show or a sound made etc.
In this case if someCondition is true then it should show the notification natively as it normally would, otherwise dont do anything.
Try calling completionHandler(.alert) in this method in your app and your notifications should show
Checkout the documentation here

Local notification data

Hello to all I'm stuck with one issue in my iOS app. I have searched over the internet and didn't find solution. Most of the solutions are using deprecated
func application(_ application: UIApplication, didReceive notification: UILocalNotification)
So scenario is following:
App schedules for each day 5 local notifications and that is okay and that works:
var dateComponents = DateComponents()
dateComponents.weekday = Date().dayNumberOfWeek()
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = 5
let notificationTrigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let request = UNNotificationRequest(identifier: "identifier", content: notifcation, trigger: notificationTrigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
But my question is: is it possible to find out when notification is fired regardless of app state and get data from that notification?
Because time for each notification is retrieved from API I don't know when (hour and minute) notifications will be scheduled.
For example in Android I schedule notifications using AlarmManager API and than easly by extending BroadcastReciver I can fetch every fired notification and get data sent via Intent.
I need to find out when last (fifth) notification is fired for that day so I can preform some additional work.
In short no, it's not possible to be notified regardless of the app state.
If the app is running in foreground you can do this using UNUserNotificationCenterDelegate methods, but if your app is not in foreground the app will know about the notification only if it's opened from the notification (swiping or tapping it).

How to change iOS notification message upon receiving?

When I push notification from OneSignal, I want to push something like
", you have received a message"
I want to replace $name in app with the username something like
notificationMessage = UserDefaults.standard.string(forKey: "username") + notificationMessage
Is it possible to override notification?
If you mean to change the alert that the System shows, then NO you can't change those. They are managed by the OS.
For foreground only:
If you have some internal alert that you'd like to pop—when the app is in foreground then you're free to do as you wish
For example you could do something like:
func userNotificationCenter(center: UNUserNotificationCenter, willPresentNotification notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
//1. extract notification data
let message = notification.request.content.body
let title = notification.request.content.title
// 2. use the message and title and change their values
// 3. use your new message and title and show your own custom alert.
// 4. I excluded the alert so you could show whatever you like yourself. But still I want to increase the badge and have sound when notification arrives...
completionHandler([.badge, .sound])
}
you can't change the request itself since it's a get only...
Having that said I don't suggest this. Your logic of this should be handled on the server you push these notifications. This should be unnecessary.

iOS 11- User local notification which repeat every x minutes

In iOS 11, how can I implement a local notification which repeats every x minutes?
The repeating interval will be selected from the user. So for example let's say that a user choose to set a notification which will trigger tomorrow at 9:00 AM and from there it triggers every 2 days (or two weeks or 6 months or 10 minutes)
var repeatInterval = Bool()
trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: repeatInterval)
//Schedule the Notification
let identifier = titleNospace
let request = UNNotificationRequest(identifier: identifier!, content: content, trigger: trigger)
self.center.add(request, withCompletionHandler: { (error) in
if let error = error {
print(error.localizedDescription)
}
})
UNUserNotificationCenter.current().add(notificationRequest, withCompletionHandler: nil)
With this code I can schedule a notification at a set date. I've been told that from here if I would like to schedule a repeat notification I should use a triggerInterval when the notification is delivered.
But how can I do so? How can I get the value of the repeating time (defined by the user) when the notification is delivered?
Shall I use this?:
func didReceive(_ notification: UNNotification)
I've tried but it seems that it's never called.
I'm new to swift and I've tried and tried but it seems I cannot find a solution.
I've been able to manage the repeating hourly, monthly, daily and yearly.
If I would like to add a custom repeat though I really wouldn't know how to do.
For your information you can't do customise repeat time interval like every 2 min, 10 min or Etc. you must use value of NSCalendarUnit like
How to set repeat frequency in User Notification

IOS 10.2 UserNotifications problems on simple Alert and Badge

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.

Resources