IOS trigger notification action when app is in the background - ios

My app does the following when the app is opened from a remote notification. Basically, it saves article_id in UserDefaults so that I can use it when the app is launched:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void
) {
let userInfo = response.notification.request.content.userInfo
if let aps = userInfo["aps"] as? [String: AnyObject] {
let article_id = aps["article_id"]
UserDefaults.standard.set(article_id, forKey: "notification_article_id")
}
completionHandler()
}
}
However, this only works if the app is completely closed. If the app remains in the background and the user clicks the notification (e.g. from the lock screen), the function above will not be triggered. Thus, it will not save the data into my UserDefaults. Does any one know how to trigger a similar action in this situation? Thanks in advance!

Your extension delegate function declaration is correct, and should fire in the state that you described (lock screen, home screen, app backgrounded). Please make sure that you have set the delegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
}
If this is already set, I would verify that your parsing code is correct, and test on the simulator or another device. I often use xcrun simctl to test push notifications in the simulator. You can do this by creating a dummy test file called 'payload.json', and exectuting the following command in the terminal:
xcrun simctl push booted com.yourapp.bundle.id payload.json
This is an example of a payload.json:
{
"Simulator Target Bundle": "com.yourapp.bundle.id",
"aps":{
"alert":{
"title":"Egon Spengler",
"body":"I collect spores, molds, and fungus"
},
"sound":"alert.caf",
"badge":3
},
"alert":{
"alertUuid":"asdfasdfasdfasdfasdf",
"state":1,
"lastUpdate":"2020-11-8T21:43:57+0000"
}
}
If the application has been terminated, you can obtain notification content at launch using the following code within didFinishLaunchingWithOptions:
let notificationOption = launchOptions?[.remoteNotification]
if let notification = notificationOption as? [String: AnyObject] {
}
Finally, make sure that you have enabled 'Remote Notifications' background mode in your project settings:

Related

iOS app loads buggy when launching from a remote notification

My iOS app launches buggy (no launch image, safe areas aren't observed, tab bar doesn't show, etc.) when it's opened from a remote notification (when the user taps a push notification to open the app from a terminated state). I don't want the push notification to have any special functionality at all, I want it as plain as possible.
This is how I have my notifications configured in the App Delegate:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setupUserNotifications()
return true
}
private func setupUserNotifications() {
let notifications = UNUserNotificationCenter.current()
let connectionMessages = UNNotificationCategory(identifier: "connectionMessageNotification",
actions: [],
intentIdentifiers: [],
options: UNNotificationCategoryOptions.init())
let interactionMessages = UNNotificationCategory(identifier: "interactionMessageNotification",
actions: [],
intentIdentifiers: [],
options: UNNotificationCategoryOptions.init())
notifications.delegate = self
notifications.setNotificationCategories([connectionMessages, interactionMessages])
notifications.requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
} else {
print(if: error)
}
}
}
And this is how I handle the delegate (doing nothing and just calling completion):
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
print("did tap remote notification")
completionHandler()
}
Is there a step I'm missing? Am I supposed to implement application(_:willFinishLaunchingWithOptions:)? What I don't understand is that if the user launches the app by tapping a push notification, shouldn't the app just launch normally without having to take any extra steps?
The problem was that my app was configured to receive background push notifications and it would launch the app in the background (before the user tapped on the notification) and so when the user did tap on the notification to launch the app it would sometimes, I guess, catch it in some intermediate state and the UI would look all funky. I assume that there are extra steps that one would need to take to handle launching the app through a background notification. Regardless, by disabling background notifications, the app now launches when the user taps on the push notification as normal. Whew.

launchOptions always nil when launching from a push notification

I'm sending push notifications from a Django app (using django-push-notifications) to an iOS app. The app targets iOS 13 and I'm running it on an iPhone 7 running iOS 13.3.1. I'm debugging in Xcode 11.3.1
I'm trying two different methods to send the notification from the Django side:
Method 1:
devices.send_message(message={"title" : title, "body" : message}, thread_id="events", extra={"foo": "bar"})
Method 2:
devices.send_message("[will be overwritten]", extra={
"aps": {
"alert": {
"title": "Bold text in the notification",
"body": "Second line in the notification"
},
"sound": "default",
},
"foo": "bar"
})
As far as I can tell, both methods should result in a payload which looks like Method 2.
I'm debugging by doing the following:
Set "wait for executable to be launched" in my device scheme
Build and run in Xcode
Ensure app has been killed in the task switcher
Trigger sending of remote notification
Tap on received notification to launch app
No matter what I do, launchOptions is always nil. I've tried setting a breakpoint to inspect the variables. I've tried using os_log to log to the console if launchOptions is not nil, and I've tried triggering an alert (following advice from this question) to rule out Xcode debugger interference. It's always nil.
My AppDelegate currently looks like this:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let notificationOption = launchOptions?[.remoteNotification]
let alert = UIAlertController(title: "Your title", message: notificationOption.debugDescription, preferredStyle: .alert)
let cancel = UIAlertAction(title: "Cancel", style: .default, handler: { action in
})
alert.addAction(cancel)
DispatchQueue.main.async(execute: {
application.windows.first!.rootViewController?.present(alert, animated: true, completion: nil)
})
return true
}
The alert triggers, but the alert content simply reads "nil".
I can't figure out what's missing. It's possible that my notification payload isn't exactly what I think it is (I've asked on the Github page for django-push-notifications to confirm if there's an issue on that end). It's also possible I've missed a step in setting up remote notifications, but I do reliably receive the notifications and they display as I expect, so they seem to be working.
Any advice greatly appreciated!
In iOS 13.0 When the app is killed, if you tap on notification, would like to open the app and get hold of notification payload. Here is how you do it.
Please check for connectOptions under sceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//look for remote notification response
if let response = connectionOptions.notificationResponse{
print(response.notification.request.content.userInfo)
}
guard let _ = (scene as? UIWindowScene) else { return }
}
I didn't find a solution to this issue, but I found a workaround. I still have no idea why launchOptions was always nil, but I've been able to access the payload by doing the following:
In AppDelegate.swift:
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
...
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UNUserNotificationCenter.current().delegate = self
return true
}
...
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let actionIdentifier = response.actionIdentifier
switch actionIdentifier {
case UNNotificationDismissActionIdentifier: // Notification was dismissed by user
// Do something
completionHandler()
case UNNotificationDefaultActionIdentifier: // App was opened from notification
// Do something
completionHandler()
default:
completionHandler()
}
}
If I then set a breakpoint in userNotificationCenter, I can dig out the notification payload:
It seems that after changes in iOS 13 we don't have to process notifications in didFinishLaunchingWithOptions function.
We can just use:
extension AppDelegate: UNUserNotificationCenterDelegate{
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let aps = userInfo["aps"] as? [String: AnyObject] {
// Do what you want with the notification
}
completionHandler()
}
}
It works for any scenario when the user clicks on a notification.

Notification from Firebase stopped appearing on iOS

Last night I was testing Push Notifications from Firebase in my iOS app, and it was working as expected
I was able to send a notification from at Cloud Function to a specific FCM token.
This morning notification doesn't arrive when using the same method.
Cloud Function
Here's the function that I use to send the notification:
function sendNotification(title: string, body: string, token: string): Promise<void> {
const message: admin.messaging.Message = {
apns: {
headers: {
'apns-priority': '10'
},
payload: {
aps: {
alert: {
title: title,
body: body,
},
sound: "default"
}
}
},
token: token
}
return admin.messaging().send(message).then(response => { console.log(`Notification response ${response}`) }).then(justVoid)
}
Here token is the token I received from the InstanceId in the iOS app.
When this function is triggered, I see the following in Firebase web console's Cloud Function log:
Notification response projects/project-name/messages/0:1571998931167276%0f7d46fcf9fd7ecd
Which, as far as I understand, is a success message. So I'm expecting the notification to show up on the device at this point, but nothing.
iOS App
I've followed this guide to troubleshoot, and am sure that the setup is right: https://firebase.google.com/docs/cloud-messaging/ios/first-message?authuser=0
I did try to re-install the app on the device on which Im testing: I've verified that the app does through these step after re-install:
call: UNUserNotificationCenter.current().requestAuthorization(options:, completionHandler:)
call: UIApplication.shared.registerForRemoteNotifications()
listen to updated FCM token by implementing: func messaging(_ messaging:, didReceiveRegistrationToken fcmToken:)
call: InstanceID.instanceID().instanceID(handler:)
double check that notifications is allowed for my application in the iOS settings app.
Test Notification from console
I've tried sending a Test Notification in from Notification Composer, using a the recent FCM token for the test device, but this notification doesn't show up either, and it doesn't give me any feedback on screen whether the notification is successfully sendt or not.
What am I doing wrong here?
Any Suggestions to how I can debug this issue?
When we are working with Push Notification then it is very harder to debug issue.
As per my experience, there is nothing wrong with your typescript or iOS code because earlier it was worked perfectly. Below is the possible reason if the push notification is not working.
Generally, the APNS related issue occurs due to the certificate.
Make sure you are uploading the correct APNS profile to the firebase
console for both development and release mode.
Make sure you have enabled the notification in capabilities.
Your Project -> capabilities -> turn on Push Notifications
Make sure you're added correct GoogleService-Info.plist to the
project.
APNS Certificate is not expired.
Make sure you're using the latest version of the firebase.
Your device time should be automatic, not feature time.
If you still think it is code related issue and the above solution is not working then you can go with the below wrapper class of HSAPNSHelper class which is created by me and it is working for iOS 10 and later .
import Foundation
import UserNotifications
import Firebase
class HSAPNSHelper: NSObject {
static let shared = HSAPNSHelper()
let local_fcm_token = "local_fcm_token"
var notificationID = ""
func registerForPushNotification(application:UIApplication){
FirebaseApp.configure()
self.getFCMToken()
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
DispatchQueue.main.async {
self.getFCMToken()
}
}
application.registerForRemoteNotifications()
}
}
extension HSAPNSHelper:UNUserNotificationCenterDelegate {
fileprivate func getFCMToken() {
InstanceID.instanceID().instanceID(handler: { (result, error) in
if error == nil, let fcmToken = result?.token{
print("FCM Token HS: \(fcmToken)")
UserDefaults.standard.set(fcmToken, forKey: self.local_fcm_token)
}
})
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("didFailToRegisterForRemoteNotificationsWithError : \(error)")
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
var token = ""
for i in 0..<deviceToken.count {
token = token + String(format: "%02.2hhx", arguments: [deviceToken[i]])
}
Messaging.messaging().apnsToken = deviceToken
getFCMToken()
}
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo as! [String: Any]
print(userInfo)
completionHandler([.alert, .badge, .sound])
}
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
print(userInfo)
self.notificationRedirection()
}
private func notificationRedirection(){
}
func fcmToken() -> String{
return UserDefaults.standard.string(forKey:local_fcm_token) ?? ""
}
}
How to use?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//For Push notification implementation
HSAPNSHelper.shared.registerForPushNotification(application: application)
return true
}
Use fcmToken() to get current FCM token.

Show fcm notification if meet a condition with swift

I am using firebase push notification, where my app is subscribed to a topic, all is good. But I want to know if it is possible to show the notification if pass a notification. this is my scene:
local_user_id = 10
var payload = {
notification: {
title: "hi",
body: "this is a notification",
sound: "default"
},
data: {
user_id: "1",
message: "you should pay $3020.25"
}
};
1) control if user is_login (true/false)
2)get the message data of notification and check:
if (payload.data.user_id = local_user_id && is_login){
show_notification()
}
3) show notification
Actually I only have the notification and no more, I am new with firebase, this is my code:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
(granted, error) in
}
application.registerForRemoteNotifications()
FirebaseApp.configure()
return true
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
let dict = userInfo["aps"] as! NSDictionary
let message = dict["alert"]
print("response")
print(message)
}
I don't know how to do that what I want, is it posible?
thanks in advance
You can not control show hide notification in your application. You can put some logic on the backend side, whether this notification should be displayed or not. Nevertheless, I have workaround below possible way.
Use a silent push. Then trigger local notifications. Note: Silent
push isn't always reliable.
So just include content-available: 1 in your payload as shown
below to get a silent notification.it will act as silent notification.
Also in Info.plist should have UIBackgroundModes set to
remote-notification
but it'll be limited to Running and background mode only. you won't be able to receive or handle it if content-available is set to 0 while your app is offline
If you are trying to just present the notification to the user while the app is running in the foreground, you would need to have your AppDelegate conform to the UNUserNotificationCenterDelegate. This is because when the application is running, the notifications will be presented to the UNUserNotificationCenter shared object.
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void {
let content = notification.request.content
if content.data["user_id"] == local_user_id && is_login {
completionHandler(.alert)
} else {
completionHandler([])
}
}
}
You would want to make sure that the completionHandler is executed at some point in this block, because this is the handler that does the presentation of the notification. If you want a silent notification, you can use completionHandler([]) to silence the alert. Other possible options for the completionHandler are available here.

APNS Silent Remote Notification not triggering - SWIFT 3

Scenarios:
1) Silent or Normal payload when App's in Foreground:
Nothing happens
2) Silent payload when App's in Background:
Nothing happens
3) Normal payload when App's in Background:
If User click the notification to open the App.
triggers the application:didReceiveRemoteNotification:fetchCompletionHandler
If user open the App clicking the App icon:
Nothing happens
These are the payloads I'm using for the APNs:
Normal payload: .
{
"aps":{
"alert":"andre test",
"badge":0,
"sound":"default",
"content-available":1
},
"acme-syncalarm":"true"
}
Silent payload: .
{
"aps":{
"content-available":1
},
"acme-syncalarm":"true"
}
I've implemented the Remote Push Notification using this code:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Receeeeeeeeived: \(userInfo)")
UIApplication.shared.applicationIconBadgeNumber = 11
completionHandler(.newData)
}
I also implemented this to check if the App is recovering from a kill state (as I've read in some Questions too), but the code never enters the print(rn)line.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if let rn = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] {
print(rn)
}
}
EDIT 1 -
I have also enabled Remote Notifications in background mode for the App.
What do I have to do to cover the "Nothing Happens" Scenarios? 1 , 2 and 3.2 ?
Some notes:
"If user open the App clicking the App icon: Nothing happens" <-- That's expected, because you didn't interact with any notification directly. Imagine if you had 5 notifications arrived. How would you know which notification you should process...
normal payload won't have any key named content-available. So that again is a silent notification. Can you first see my answer here?
Some suggestions:
Make sure you've enabled Remote Notifications in background mode. Like this:
Additionally See here. iOS 11 initial releases were buggy for silent notifications. Make sure you have the latest version for your testing, otherwise it won't work. If you have an iOS 10 device, then first try testing with that...
Make sure you have Background App refresh and notifications available on your device. To see how to do it, refer to my linked answer.
Are you creating the payload yourself or you're using FireBase? If you're using Firebase then some of the keys change...and you must adjust accordingly.
make sure you've set some object as the delegate of UNUserNotificationCenterDelegate e.g.:
UNUserNotificationCenter.current().delegate = delegateObject
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let content = notification.request.content
// Process notification content
completionHandler([.alert, .sound, .badge]) // Display notification as regular alert and play sound
}
Code copied from here.
If you don't do such then you won't be showing any notification when the app is in the foreground. This should resolve the issue of when app is in foreground and you've received a normal remote notification.

Resources