I have built an app that supports push notifications. I am trying to figure out how to wake my app when I get a push notification so that I can kick off a download process to update my app data. The problem I am running into is when the app is in the background it doesn't receive the didReceiveRemoteNotification even though the push notification is shown on the device and I do include the content-available attribute in the aps payload. Please see relevant code samples below.
Screenshot of my capabilities tab in Xcode.
Please note I have also tried to enabled Background fetch but that doesn't do anything different.
Here is how I register for remote notifications
let rejectAction = UNNotificationAction(identifier: "reject", title: "Reject", options: [.destructive])
let acceptAction = UNNotificationAction(identifier: "accept", title: "Accept", options: [.foreground])
let actionCategory = UNNotificationCategory(identifier: "ACTIONABLE", actions: [rejectAction, acceptAction], intentIdentifiers: [rejectAction.identifier, acceptAction.identifier], options: [.allowInCarPlay])
let center = UNUserNotificationCenter.current()
center.setNotificationCategories([actionCategory])
center.requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
}
UIApplication.shared.registerForRemoteNotifications()
Here is how I respond to remote notifications
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
NSLog("** [VoiceAppLog] Push Notification Received - didReceiveRemoteNotification **");
do {
let json = try JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted)
let str = NSString(data: json, encoding: String.Encoding.utf8.rawValue)
NSLog("[VoiceAppLog] userInfo: \(String(describing: str))")
} catch {
}
// Make API calls here
completionHandler(.newData)
}
Here is a sample of the content payload I am sending with slight modifications to de-identify the product I'm working on.
{
"my_category" : 1,
"custom_attribute_boolean" : false,
"aps" : {
"content-available" : 1,
"alert" : "Test Message",
"badge" : 1,
"sound" : "custom_sound.caf"
}
}
As you can see and from what I can tell I am doing everything correct based on the documentation. One thing that is interesting is when I have the app connected to my debugger and I put the app in the background, then the didReceiveRemoteNotification is called correctly. However, as soon as I disconnect the debugger it is no longer called. This happens even if I reopen the app and background it without force quitting the app. The same occurs for production builds via TestFlight. As soon as the app goes into the background then the didReceiveRemoteNotification protocol method is no longer called.
I am curious if I'm running into the iOS bug as explained here. Do any of you all out there in Internet Land have any ideas how to resolve/work around this issue?
Thanks!
The way I resolved this issue was to use a VoIP push notification, and then respond to that in my app delegate and update content. A VoIP push notification will always wake an app regardless of the app state. I know it's not necessarily the correct use for a VoIP notification, but it works :)
Related
My app has a chat service, when new notification is received, I want to clear the notifications between user1 and user2 except the new one.
I can do it when app is in foreground by calling:
UNUserNotificationCenter.current().getDeliveredNotifications { notifications in
print("count: \(notifications.count)")
for notif in notifications {
let nUserInfo = notif.request.content.userInfo
let nType = Int(nUserInfo[AnyHashable("type")] as! String)
if nType == type {
let notifId = notif.request.identifier
if notifId != notification.request.identifier {
center.removeDeliveredNotifications(withIdentifiers: [notif.request.identifier])
}
}
}
where type is a customValue.
How to do this when app is in background or closed by a user.
You need to turn on Background Modes capability and check Remote notifications mode. In order to delete the notification in background, you need to send a new notification with no alert, like {"aps": {"content-available": 1}, "del-id": "1234"}, where content-available means (you can check more about here Apple push service)
Include this key with a value of 1 to configure a background update notification. When this key is present, the system wakes up your app in the background and delivers the notification to its app delegate. For information about configuring and handling background update notifications, see Configuring a Background Update Notification.
and del-id will be the id of the notification you want to delete, you can use an array as well. You can put these information with together with your message notification as well.
In your AppDelegate.swift you will need to add this method to delete the notification in background. In your case, you can send the id of the notification you does not want to delete and use your method to delete all delivered notification except the one with the id you send in your last notification.
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
guard let idToDelete = userInfo["del-id"] as? String else {
completionHandler(.noData)
return
}
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [idToDelete])
completionHandler(.noData)
}
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.
This question already has answers here:
What is Silent Push Notification? When does the device receive it?
(2 answers)
Is possible use silent push to wake up APP and get the VOIP call?
(1 answer)
Closed 5 years ago.
I know this is not the first version of this kind of question - but all information I found seems to be outdated or even wrong. So I decided to ask the question again.
Currently I'm using remote notifications to send notifications to my iOS device. Because I'd like to "awake" my application every hour (even if the app was force closed by the user) my idea was to use silent-push notifications.
Just sending Notifications is working quite well - even in the background or after force-closed by the user. But how to wake my application when it's force-closed to perform a background task by using silent-push-notifications?
func application( _ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let aps = userInfo["aps"] as! [String: AnyObject] { // remote information
completionHandler(.newData) // call completion handler
}
This is the raw of the notification:
send notification but doesn't perform background task (doesn't awake my app)
{
"aps" : {
"alert" : {
"title" : "..."
},
"content-available" : 1,
"information" : "abc"
}
}
also doesn't perform background task (doesn't wake my app)
{
"aps" : {
"content-available" : 1,
"information" : "abc"
}
}
Using the TwilioChatClient pod, I have successfully registered my app to Twilio Programmable Chat to receive APN notifications.
However, from what I can tell, these notifications are being created after calling client.register(withToken: deviceToken) on an instantiated TwilioChatClient client, and NOT through the application's AppDelegate didReceiveRemoteNotification method. Stranger yet, didReceiveRemoteNotification is called, but only when the application is in the active state, and not the background state, where I would like to perform some operations.
Does anyone know where and how these notifications are being created, or why didReceiveRemoteNotification is only called during the active state? Amongst other things, I would like to increment the application icon badge number with each notification sent out by the client.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("Registered for notifications");
if UserUtils.client?.userInfo != nil {
print("Has info");
UserUtils.deviceToken = deviceToken;
UserUtils.client?.register(withToken: deviceToken)
} else {
print("No info");
updatedPushToken = deviceToken as NSData?
}
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Received a notification");
if UIApplication.shared.applicationState != .active {
print(userInfo);
UserUtils.client?.handleNotification(userInfo);
UIApplication.shared.applicationIconBadgeNumber += 1;
if UserUtils.client?.userInfo != nil {
print(userInfo);
let jsonNotification = JSON(userInfo["aps"])
let alert = jsonNotification["alert"].stringValue + "\"}";
print(JSON.init(parseJSON: alert)["body"]);
} else {
print(userInfo);
let jsonNotification = JSON(userInfo["aps"])
let alert = jsonNotification["alert"].stringValue + "\"}";
print(JSON.init(parseJSON: alert)["body"]);
}
} else {
}
}
where the client.register(withToken: deviceToken) works as intended.
Twilio developer evangelist here.
I've spoken with the Programmable Chat team and this is what I've found out:
application(_:didReceiveRemoteNotification:fetchCompletionHandler:) is for silent notifications in the background that perform background processing only (that is, with "content-available": 1 set in the APNS notification). Programmable Chat sends notifications that show information to the user, so it won't be fired in background mode
Notifications can update the badge count for you though, so this is processing you don't have to do, this requires a different setting in the notification that we currently do not support, however work is being done to add that support now
If you want to both show a notification and do further background processing, this is not supported in regular notifications, however this is supported with iOS 10's service extensions. Programmable Chat doesn't support those either, but again, it is being worked on, so you may see that soon
Keep an eye on the Programmable Chat Changelog for these additions.
Let me know if that helps at all.
I am searching the way about how to handle push notification payload data as soon as the notification reaches to the client app without opening or tapping it.And I am still not getting the data unless the user tap or open it from notification center or banner or alert.The function didReceiveRemoteNotification only triggered when the user click the notification on the screen.So,how to get the notification payload data when the notification arrive to client app even the user ignore(without open or tap) it.
INFO : I heard that GCM(Google Cloud Messaging) can make notification handler if the client app user tapped the notification or not.It can catch the notification payload json data as soon as it reach the client app without even need user to tap or open it.Is that right?
I really need a hand to pick me up with getting notification payload data on ios without even need a user to open or tap it.
Update : The app is still running on device which mean it was active.I can get the payload data when i click my notification which was "{aps:} json i get it.But,I still cant get the data when i don't open the notification"
Here is my state
When the app was at foreground,I get the data.
1.I run the App,
2.Send Notification,
3.Get the notification with an alert,
4.I get the data(payload).
Work fine when app is active.
But,when the app reach to background
1.Run The app,
2.Close The App by pressing home button,
3.Send Notification,
4.Get the notification.
5.But,cant get the data until i click notification that I was receive at banner
or notification center.
But,when i click the notification at banner or notification it went to app and then i get the data.
Is there anyway that i can get the data if the app in background when the notification received.
Here is the code :
import UIKit
import RealmSwift
let realm = try! Realm()
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var data : [NSObject : AnyObject]!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//one singnal is the push notification service that i use for push notification.
let oneSignal = OneSignal(launchOptions: launchOptions, appId: "__app_id__") { (message, additionalData, isActive) in
NSLog("OneSignal Notification opened:\nMessage: %#", message)
if additionalData != nil {
NSLog("additionalData: %#", additionalData)
}
}
oneSignal.enableInAppAlertNotification(true)
return true
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
print("User Info : \(userInfo)")
if let custom = userInfo["custom"] as? NSDictionary{
if let a = custom["a"] as? NSDictionary{
print("A : \(a)")
}
}
}
I came across the same problem. As mentioned in the previous comments, this post is quite helpful.
According to Apple,
When a remote notification arrives, the system displays the
notification to the user and launches the app in the background (if
needed) so that it can call this method. Launching your app in the
background gives you time to process the notification and download any
data associated with it, minimizing the amount of time that elapses
between the arrival of the notification and displaying that data to
the user.
The first thing you have to do is to allow your app to do something when in background. You do this by adding Required background mode in your info.plist, then add it App downloads content in response to push notifications. Your info.plist should look something like this:
Now this is done, your app should awake when it receive a notification. You can execute a small code inside didReceiveRemoteNotification. Such as this.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
//do some code here
UIBackgroundFetchResult.NewData
}
Note that you have to pay attention to the completionHandler:
As soon as you finish processing the notification, you must call the
block in the handler parameter or your app will be terminated. Your
app has up to 30 seconds of wall-clock time to process the
notification and call the specified completion handler block.
Let me know if everything is clear :)