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

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

Related

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

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?

iOS : How to remove remote notifications if app is not active?

I want to clear remote notifications so they don't add up in the Notification Center (like when you get a video call in WhatsApp or Messenger, only the last notification is displayed).
I tried to call (in didReceiveRemoteNotification):
let center = UNUserNotificationCenter.current()
center.removeDeliveredNotifications(withIdentifiers: ["notification_identifier"])
But it gets called only if the app is active. How can I do this if the app is in another state?
Thanks for your help.
After some research and thanks to Paulw1's answer, I found out there are two ways of doing this:
Remote only
Notifications can be collapsed remotely, you only have to send the notification with apns-collapse-id as a request header. Please note that it's only supported in HTTP/2 though. More information here.
Silent remote + local notification
The other way consists in sending a silent remote notification, with this kind of payload:
{
"type": "notification_type",
"aps" : {
"content-available": 1
}
}
It will call didReceiveRemoteNotification even if the app's state is inactive or background. Then, I create a local notification request (needs using UserNotifications, available from iOS10) :
let content = UNMutableNotificationContent()
content.body = "Notification message"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.25, repeats: false)
let request = UNNotificationRequest(identifier: "identifierToUpdate", content: content, trigger: trigger)
self.center.add(request, withCompletionHandler: nil)
The key to update the previous notification is to use the same request identifier.

Schedule a local notification(silent) to perform a custom task in background + swift ios

In our app, the user will clock-in when he starts his work. If the user forgets to clock-out, we will have to automatically clock him out after 24 hours from the clock-in time. The app might not be in the active/background state for such a long time. It might be terminated. So our idea is to post a local notification through which will execute the code in the background to clock him out. This notification has to be a silent notification. But our understanding from the research is that local notifications cannot be silent. So is there any other way we could achieve this? Or can we actually schedule a silent local notification?
class func generateLocalNotificationWith(timeInterval : TimeInterval, title : String, message : String, mobileTimeClockId: Int)
{
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
let content = UNMutableNotificationContent()
//adding title, subtitle, body and badge
content.title = title
content.body = message
let userInfoDictionary = ["badge":"0","content-available": "1"]
let dict = ["aps": userInfoDictionary, "MobileTimeClockId": mobileTimeClockId] as [String : Any]
content.userInfo = dict
//getting the notification trigger
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
//getting the notification request
let request = UNNotificationRequest(identifier: "SimplifiedIOSNotification", content: content, trigger: trigger)
//adding the notification to notification center
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
If you want a notification to be silent and do background work it has to be a push notification. A local notification can't wake your app without some user interaction.
The best way to do what you want is to use Core Location region monitoring to wake your app when the user physically leaves their workplace. Region monitoring can wake your app silently in the background and let you do some work. The problem is that it's not 100% assured that it will be triggered and the user will need to accept background location permissions.

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.

How to dismiss 3D-touched notification with custom action?

I am implementing a kind of alert notification for my app, using UserNotifications. When 3D-touching this notification, I want it to display some 'alert information', and the option to "Snooze" the alert, which will effectively repeat the notification in 5 minutes.
This is all working fine, except that the notification doesn't get dismissed when I click the Snooze button.
I am using a NotificationContentExtension to modify the content, with the categoryIdentifier "ReminderNotificationExtensionCategory". Then I have created my category in code like this:
let snooze = UNNotificationAction(identifier: "snoozeActionIdentifier", title: NSLocalizedString("SNOOZE", comment: ""), options: .foreground)
let reminderCategory = UNNotificationCategory(identifier: "ReminderNotificationExtensionCategory", actions: [snooze], intentIdentifiers: [], options: .customDismissAction)
UNUserNotificationCenter.current().setNotificationCategories([reminderCategory])//Not sure if I should set this every time or just once..
Then I create my notification object
let content = UNMutableNotificationContent()
content.categoryIdentifier = "ReminderNotificationExtensionCategory"
content.title = "test"
content.body = "test"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false) //Fire in 3 seconds
let request = UNNotificationRequest(identifier: "someIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error:Error?) in /**/}
Now, my notification will be sent in 3 seconds.
I lock my screen and receive it, and it looks great. When I 3D-touch it, my extension is launched, and I get a sneak peak on a UIView I have set up, along with my "Snooze"-button below the notification, as shown in the image:(content removed)
When I click the "Snooze"-button, a UNNotificationContentExtension delegate-method will be called, specifically func didReceive(_ response: completion:)
The completion only accepts a value of UNNotificationContentExtensionResponseOption, which would be either .dismiss, .doNotDismiss, or .dismissAndForwardAction.
Just for testing, I have done this:
func didReceive(_ response: UNNotificationResponse, completionHandler completion: #escaping (UNNotificationContentExtensionResponseOption) -> Void) {
print("This is happening. ActionIdentifier: ", response.actionIdentifier)
completion(.dismiss)
}
And when I debug the notification extension, the print does really happen. It prints out "This is happening. ActionIdentifier: snoozeActionIdentifier". However, the notification is not being dismissed.
If I change from .dismiss to .doNotDismiss, nothing changes. It still does not dismiss. If I change to .dismissAndForwardAction, it dismisses and opens my app.
I can't find anyone with this problem. I want .dismiss to dismiss my notification. Why isn't it? Am I doing something wrong?
Edit
I see now that these two scenarios are happening:
I 3D-touch my notification from the lock screen, then tap outside it, my notification 'collapses' and the regular notification is visible as it was before I 3D-touched it.
I 3D-touch my notification from the lock screen, tap 'Snooze' (and nothing happens), then tap outside the notification, and the regular notification disappears.
So the completion callback actually does dismiss the regular notification, but not the extension. Why? How can I dismiss the extension? Is that a manual method I must call?
I see my stupid mistake now..
As you can see in my code above, I have instantiated the action button in my category as such:
let snooze = UNNotificationAction(identifier: "snoozeActionIdentifier", title: NSLocalizedString("SNOOZE", comment: ""), options: .foreground)
At the very end, I specify the option .foreground. This should simply be an empty array [] for my purpose.
I thought I had to specify an option, and that the input type was of UNNotificationActionOptions, which only had the options .authenticationRequired ("no I don't want that") .destructive ("no I don't want a red button") and .foreground ("well, if I have to pick one if these.."). Turns out that simply specifying an empty array [] did the trick.

Resources