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.
Related
The thing is we don't want to interact with push notifications. The one thing we want to do is to detect when device has received one while out app was active.
We are going to use this detected information to change screen brightness for example.
The thing is we just want to detect the ones shows on the screen by iOS.
if we use this in in AppDelegate.swift to get permission.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
if granted {
UIApplication.shared.registerForRemoteNotifications()
}
}
return true
}
and that to detect and to do something when received notification.
func userNotificationCenter(center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
}
Application asks permission showing
"App Name" Would Like to Send You Notifications
message.
But we do not want to send notification we just want to detect any notification showed on screen by iOS when our app was running.
There is no indication to your app when iOS displays a notification from another app if the user does not interact with it; the notification is simply overlaid on top of your screen.
If the user taps the notification then the usual app delegate methods will fire as your app moves to the background and then suspended state
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.
I know there has been a lot written about this topic but I just can't find the right answer.
Is there a way how to know when the user received remote notification and when the user clicked on one on iOS 8.
I would like to know this because when I receive it I want to save it and when user clicks on it I want to open some view.
I have found this answer https://stackoverflow.com/a/16393957/1241217 but the problem is when user is in the app and opens notification center and clicks on one, the app is not inactive and not in the background.
I also found this answer https://stackoverflow.com/a/12937568/1241217 but I know that this is ran only when the app is killed and started from new.
I also don't want to do this https://stackoverflow.com/a/32079458/1241217 since I need to detect when I received notification.
So is there a way how to know if the user only clicked on notification. As far as I understood it has to be done in didReceiveRemoteNotification but I don't know how to separate between them. And I need an answer for before iOS 10 because the app target is iOS 8.
MY SOLUTION:
So as I wrote in the comment of Shabbir Ahmad answer my solution was to remember date when the application did become active and the date when the notification was received. If the difference between this dates was a second or less I accepted that as the user clicked on the notification.
You have to implement UNUserNotificationCenterDelegate and its method
userNotificationCenter(_:willPresent:withCompletionHandler:) and userNotificationCenter(_:didReceive:withCompletionHandler:) which gets called when a user taps a notification. In willPresent: you have to call the completionHandler with an option that would indicate what should happen when a notification arrives while the app is in foreground.
Registering such a delegate is easy:
UNUserNotificationCenter.current().delegate = self
So e.g.:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(UNNotificationPresentationOptions.alert)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let userInfo = userInfo as? [String: Any] {
// TODO: implement your logic
// just don't forget to dispatch UI stuff on main thread
}
}
You can implement that delegate by AppDelegate, but also by any NSObject, I would go with the latter to keep AppDelegate as clean as possible.
P.S.: Of course, this assumes that you have been granted permissions by the user (UNUserNotificationCenter.current().requestAuthorization(options:completionHandler:)) and you are registered to accept notifications (UIApplication.shared.registerForRemoteNotifications()).
Read more in Scheduling and Handling Local Notifications, section Responding to the Delivery of Notifications - while the section is about local notifications, it is exactly the same for the remote ones (they are handled both by the same delegate).
when you click on notification in background mode before ios 10 and when you are in foreground,in both cases your below method will call,
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
So you can differentiate the behaviour,
First of all you assign a boolean variable in AppDelegate class like this:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var isUserTapOnNotification = false
after that make true isUserTapOnNotification in
func applicationWillEnterForeground(_ application: UIApplication) {
isUserTapOnNotification = tue
}
because when you tap on notification bar, your app will came in foreground and applicationWillEnterForeground will call first,
after that your didReceiveRemoteNotification will call:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if #available(iOS 10.0, *) {
//do nothing
}else { //<ios 10
if isUserTapOnNotification == true {//when app is in background and user tap on notification bar
//do action whatever you want
} else { //when user is in foreground and notification came,
//before ios10,notification bar not display in foreground mode,So you can show popup by using userInfo
}
}
after that applicationDidBecomeActive will call and you reset isUserTapOnNotification to false like this:
func applicationDidBecomeActive(_ application: UIApplication) {
isUserTapOnNotification = false
}
I hope this answer will help you.
I know similar questions have been asked many times. But it is still very confusing to me after reading those threads, especially after UNUserNotificationCenter is introduced in iOS 10.
The official documentation mentioned 3 methods where I can handle remote notifications:
Implement userNotificationCenter:willPresentNotification:withCompletionHandler: to handle a notification when the app is in foreground.
Implement userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: when the app is in background or not running.
But the documentation also mentioned: In iOS and tvOS, the system delivers the notification payload to the application:didReceiveRemoteNotification:fetchCompletionHandler: method of the app delegate.
So,
To handle a remote notification when app is in background/inactive, should I put my code in application delegate method in 3, or the notificationCenter delegate in 2? Since UNUserNotificationCenter is only available for iOS>10, should I write different code to handle each case?
About 1, it is only available after iOS 10. How can I handle remote notifications when app is running in foreground before iOS 10?
And, more confusing: In case the app is in background, when are the delegate methods called: when the notification message is received? or when the user taps the notification?
Related: iOS push notification: how to detect if the user tapped on notification when the app is in background?
iOS 10 and later:
1) userNotificationCenter willPresent notification: Generally used to decide what to do when user is already inside the app and a notification arrives. You could possibly trigger a remote notification inside the app. After the user taps on the remote notification, method 2 (didReceive response) gets called.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (_ options: UNNotificationPresentationOptions) -> Void) {
//Handle push from foreground
//When a notification arrives and your user is using the app, you can maybe notify user by showing a remote notification by doing this
completionHandler([.alert, .badge, .sound])
//To print notification payload:
print(notification.request.content.userInfo)
}
2) userNotificationCenter didReceive response: Generally used to redirect the user to a particular screen of the app after user taps on the notification.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
//Handle push from background or closed (or even in foreground)
//This method is called when user taps on a notification
//To print notification payload:
print(response.notification.request.content.userInfo)
}
Below iOS 10:
3) application didReceiveRemoteNotification:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
//To print notification payload
print(userInfo)
if #available(iOS 10.0, *) {
}
else {
//Handle remote notifications for devices below iOS 10
if application.applicationState == .active {
//app is currently in foreground
}
else if application.applicationState == .background {
//app is in background
}
else if application.applicationState == .inactive {
//app is transitioning from background to foreground (user taps notification)
}
}
}
4) application didFinishLaunchingWithOptions launchOptions: The only scenario which is left for devices below iOS 10 is when app is closed and user taps on the notification launching the app. You'll have to check the following method for this scenario.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//To print notification payload:
if let notification = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
print(notification)
}
}
LaunchOptions is a dictionary indicating the reason the app was
launched (if any). The contents of this dictionary may be empty in
situations where the user launched the app directly.
Now to answer your questions,
To handle a remote notification when app is in background/inactive, you'll have to add your code in method 2 (userNotificationCenter didReceive response) for devices with iOS 10 and above. Also, you'll have to use method 3 (application didReceiveRemoteNotification) for devices below iOS 10.
To handle remote notifications when app is running in foreground before iOS 10, use the method 3 active state.
In addition to the great answer by Ameya, I wanted to point out that userNotificationCenter:willPresent:notification does not get called if app is in background state.
My complete solution to handle all cases on iOS 10+ would be to also use application:didFinishLaunchingWithOptions:launchOptions, and check if in background state, and handle the notification there too. Your payload, however, now also needs to include the "content-available": 1 field).
I am creating a notification service using swift3 in xcode 10.
The problem now is that when a push notification comes in the background (even when the app is closed), the badge does not increase at first, but increases by 1 from the second push notification.
Furthermore, when I enter the app and come back in the background, the number of badges will be normal, but the above problem will happen again.
I tried to check the problem through delay or local notifications, but I have not been able to figure out what the problem is.
Below are the notifications related to the notifications within the AppDelegate. Push Notification Click event also works normally.
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate, NaverThirdPartyLoginConnectionDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert,], completionHandler: {(granted, error) in
if (granted)
{
application.registerForRemoteNotifications()
}
else{
}
})
return true
}
...
...
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.badge, .alert, .sound])
UIApplication.shared.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("userInfo: \(response.notification.request.content.userInfo)")
var userInfo:[AnyHashable: Any]?
let pushId:Int32 = userInfo?["uid"] as! Int32
self.moveView(pushId)// My app load method
}
}
Running app in the background state is just a brief stop on the way to the app being suspended. While suspended, an app remains in memory but does not execute any code. For this reason your code is not executing and thus badge value does not update. See these below link to about application state and background execution.
Application State
Background Execution
So better approach to solve this problem is to send send badge value inside of push notification payload. e.g
{
"aps" : {
"alert" : {
"title" : "Game Request",
"body" : "Bob wants to play poker",
"action-loc-key" : "PLAY"
},
"badge" : 5
},
"acme1" : "bar",
"acme2" : [ "bang", "whiz" ]
}
See this link to create remote notification payload
Creating the Notification Payload
Don't increase badge value programmatically unless you need to show badge for local notification. If you want to execute code on background while push notification receive, use VoIP push notification which has few restriction e.g app must be related VoIP services.
I recommend to change the push notification payload.
Thanks.