I can get the push notification message in this method :
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
}
But the problem is how to display an alert notification when the app is runing as the same way it displayed when the app is in background.
Is that possible ?
When the app in foreground, you only get the callback, iOS doesn't show alert in that case, you have to do it yourself...
You can do it like this:
Create a nib for the 'PN foreground view', for example:
And when you get a PN in foreground, you can instantiate the view, and add it to the UIWindow with animation, code for example:
// init and configure your custom PN foreground view
let nib = UINib(nibName: self.nibName, bundle: NSBundle(forClass: PnForegroundView))
pnForegroundView = nib.instantiateWithOwner(nil, options: nil)[0] as! PnForegroundView
pnForegroundView.setWidth(UIScreen.width)
pnForegroundView.setHeight(63)
pnForegroundView.title = <some title from notification>
pnForegroundView.image = <some image from notification>
// add the view to the key window
let window = UIApplication.sharedApplication().keyWindow!
window.addSubview(pnForegroundView!)
// Change window level to hide the status bar
window.windowLevel = UIWindowLevelStatusBar
// Show the PN foreground view with animation:
self.pnForegroundView!.setBottom(0)
self.changeWindowLevelToHideStatusBar()
UIView.animateWithDuration(0.2) {
self.pnForegroundView!.setBottom(self.pnForegroundView!.height)
}
Of course, you should set a delegate for this view, for case user clicking the notification, and when the user dismisses it.
Also, you can add time for automatic dismissal.
Last thing - when removing the PnForegroundView, you better reset your UIWindow level to default value, for showing the status bar
Adding that completionHandler line to appDelegate method worked for me:
//Called when a notification is delivered to a foreground app.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("User Info = ",notification.request.content.userInfo)
completionHandler([.alert, .badge, .sound])
}
didReceiveRemoteNotification will trigger no matter whether you are foreground or in background Though, you can handle them seperately as below
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
let state = UIApplication.sharedApplication().applicationState
if state == UIApplicationState.Active {
//show alert here your app is in foreground
}
else{
//your app is in background
}
}
Related
I'm configuring push notifications in Swift. So far I have 3 scenarios.
1 - App In Foreground
In the foreground, I think I did everything correct cus I did receive the push notification data.
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("userNotificationCenter willPresent")
let content = notification.request.content
UIApplication.shared.applicationIconBadgeNumber = 0
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
completionHandler([.alert, .sound])
}
2 - User clicks on the Push Notification banner
This is also working fine.
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
print("userNotificationCenter didReceive")
defer {
completionHandler()
}
guard response.actionIdentifier == UNNotificationDefaultActionIdentifier else {
return
}
let content = response.notification.request.content
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
}
3 - App in background, then the user gets into the app
In this scenario, the push notification arrives at the user's phone. But, instead of clicking on the push notification itself, they get into the app. And I can't fetch any info from the push notification
Could anyone help on how to configure the 3rd scenario? Thank you.
you need to consider applicationState
UIApplication.State
//AppDelegate
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
switch UIApplication.shared.applicationState {
case .active:
print("Received push message from APNs on Foreground")
case .background:
print("Received push message from APNs on Background")
case .inactive:
print("Received push message from APNs back to Foreground")
}
}
When the app is background to foreground, UIApplication.State is inactive
inactive is 'The app is running in the foreground but is not receiving events.'
thus I think the best way to do the behavior you want is to write it yourself.
for example,
//AppDelegate
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
switch UIApplication.shared.applicationState {
case .active:
print("Received push message from APNs on Foreground")
case .background:
print("Received push message from APNs on Background")
case .inactive:
print("Received push message from APNs back to Foreground")
guard let nav = window?.rootViewController as? UINavigationController,
let currentVC = nav.viewControllers.last else {return}
if currentVC is 'youWantViewController' { //if you want ViewController, use notification post
let name = Notification.Name(rawValue: K.Event.pushRequest)
NotificationCenter.default.post(name: name, object: nil)
} else { //move to you want ViewController
let vc = 'yourViewController'()
root.navigationController?.pushViewController(vc, animated: true)
}
}
completionHandler(.newData)
}
I hope it will be of help.
I want to show notification in foreground for only some selected UIViewControllers.
But When I set NotificationCenter to receive notification in foreground for specific UIViewController then Swift do it for global scope. In some screen I don't want to see notification to appear in foreground and for that I have to specify in every screen to show notification in foreground or not which leads to lot of code usually unmanaged.
You can put the condition in the notification delegate method to show notification for a specific view controller or not.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let navigationController: UINavigationController = self.window?.rootViewController as! UINavigationController
if (navigationController.topViewController is FirstViewController) || (navigationController.topViewController is SecondViewController) {
//Show notification for First and Second ViewController
completionHandler([.alert, .badge, .sound])
}
else {
//Do whatever when you don't want to show notification
}
}
I hope this will be helpful to you...
I have two type of push notifications in my app Type A and Type B and according to each type I want to navigate the user to different view controls. right now the app show only the homeVC if user clicked on the
notification and can I pass value from the notifications object to the
view controls.?
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
userInfo["Type"] as? String == "TypeA" {
showNotificationA()
} else {
showNotificationB()
}
completionHandler(UIBackgroundFetchResult.newData)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
// How to identify which notification the user clicked to navigate to the right view?
if UIApplication.shared.applicationState == .inactive {
}
completionHandler()
}
Basically, the handling of push notifications consists more or less of the following steps:
Enabling Push notifications in the capabilities section of project settings
Requesting the access to use remote and local notifications, and delegating incoming notifications to your notification observer entity (class, struct)
This is done in the AppDelegate class, in a similar manner to:
UNUserNotificationCenter
.current()
.requestAuthorization(options: [.alert, .badge]) { (granted, error) in
UNUserNotificationCenter.current().delegate = MyCustomDelegateEntity
}
Implementing the func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) in your delegate, which receives incoming notifications
Notifications can be distinguished by an identifier, which is located in the UNNotificationResponse entity. Based on the id, you can decide how to react accordingly.
In your particular case, you should instantiate a desired view controller from the NotificationDelegate in a similar manner to
let rootVC = MyViewController.instantiateFrom(storyboard: "StoryboardName")
let navigationController = UINavigationController(rootViewController: rootVC)
(UIApplication.shared.delegate as? AppDelegate)?.window?.rootViewController = navigationController
The ViewController.instantiateFrom(storyboard:) content looks something like:
return UIStoryboard(name: storyboard, bundle: nil).instantiateViewController(withIdentifier: String(describing: self)) as! MyViewController
You can redirect all your notification to one controller and in this basview controller check which notification type its came from and navigate accordingly.
You can pass data from notification payload and handle accordingly
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 have an app that receives remote push notification.
I have implemented didReceiveRemoteNotification in this way:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("PUSH NOTIFICATION is coming")
let state: UIApplicationState = UIApplication.shared.applicationState
let inBackground = state == .background
let dizionario = userInfo["aps"]! as! NSDictionary
let alert = dizionario["alert"]! as! String
print(alert)
if(!inBackground){
print("APP IN FOREGROUND")
//show alert view and the if user tap 'ok' show webview
}
else{
print("APP IS BACKGROUND")
//SHOW WEBVIEW
}
}
In both cases(when app is in foreground and background) I have to show webview that add like child to root view(tabbar controller) but if app is in foreground then I have to show , before , an alert view.
My problem is that if app is in foreground I haven't problems , but if app is in background didReceiveRemoteNotification doesn't call(I don't see the print "PUSH NOTIFICATION is coming" ) and I don't understand why.
Can you help me?
N.B for testing I use APN Tester(https://itunes.apple.com/it/app/apn-tester-free/id626590577?mt=12) for send push notification
didReceiveRemoteNotification is meant to be used when the app is active.
When the app is in the background or inactive, you can activate it by pressing the action button on the remote notification. Implement userNotificationCenter(_:didReceive:withCompletionHandler:) in your app delegate.
In AppDelegate.swift :
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
// make your function call
}
didReceive mean is: when your Application is Background then you click the notification didReceive is working