How to get the content of remote notification using FCM (iOS)? - ios

I am using FCM for sending a push notification to devices using this method
func push(message: String, toUser: String) {
var token: String?
for person in self.users {
if toUser == person.username && person.firebaseToken != nil {
token = person.firebaseToken
}
}
if token != nil {
var request = URLRequest(url: URL(string: "https://fcm.googleapis.com/fcm/send")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("key=[your FCM Server Key]", forHTTPHeaderField: "Authorization")
let json = [
"to" : token!,
"priority" : "high",
"notification" : [
"body" : message
]
] as [String : Any]
do {
let jsonData = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("Error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
// check for http errors
print("Status Code should be 200, but is \(httpStatus.statusCode)")
print("Response = \(response)")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
}
task.resume()
}
catch {
print(error)
}
}
}
But the devices are just getting the message without doing anything with it.
I am trying to save the content of it, too. How can I do that? Is the only option to save it to the Firebase database?
Thanks.

You need to handle notifications through the AppDelegate. You'll first register for the notifications in didFinishLaunchingWithOptions. Because firebase doesnt have a key when it first starts up for messaging, you'll need to register to observe that value as well:
// register for remote notifications:
if #available(iOS 10.0, *) {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler: { (_, _) in })
UNUserNotificationCenter.current().delegate = self
FIRMessaging.messaging().remoteMessageDelegate = self
} else {
let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
NotificationCenter.default.addObserver(forName: NSNotification.Name.firInstanceIDTokenRefresh, object: nil, queue: nil, using: tokenRefreshNotification(_:))
and then you'll need a method to connect to FCM:
extension AppDelegate {
func connectToFCM() {
FIRMessaging.messaging().connect { (error) in
if error != nil {
print("unable to connect to FCM \(error)")
} else {
print("connected to FCM")
}
}
}
}
And a way to handle when you get a new token:
extension AppDelegate {
func tokenRefreshNotification(_ notification: Notification) {
if let refreshedToken = FIRInstanceID.instanceID().token() {
UserDefaults.standard.set(refreshedToken, forKey: "registrationToken")
}
connectToFCM()
}
}
Lastly, you'll need to actually handle the notification. The actual content of the push notification is in the 'notification' object for iOS 10 and higher:
#available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
// Receive displayed notifications for iOS 10 devices.
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
PushService.handle(userInfo) // do whatever you need with this
}
}
And to handle the other types of notifications, you fall back to the old way:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
//user tapped on notification
PushService.handle(userInfo) // do what you need with the userInfo dict here, which contains the push notification information
}

Related

iOS swift - Open notifications on specific view controller

When I get notifications I want to open my app to a specific screen. currently when a users taps on a notification the app just opens on the current screen open but how can I redirect to another screen instead. Please suggest any solutions?
Push notification manager:
import FirebaseMessaging
class PushNotificationManager: NSObject, MessagingDelegate, UNUserNotificationCenterDelegate {
let userID: String
init(userID: String) {
self.userID = FriendSystem.system.CURRENT_USER_ID
super.init()
}
func registerForPushNotifications() {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
Messaging.messaging().delegate = self
} else {
let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
UIApplication.shared.registerForRemoteNotifications()
updateFirestorePushTokenIfNeeded()
}
func updateFirestorePushTokenIfNeeded() {
if let token = Messaging.messaging().fcmToken {
let usersRef = FriendSystem.system.USER_REF.document(userID)
usersRef.setData(["fcmToken": token], merge: true)
}
}
func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
updateFirestorePushTokenIfNeeded()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
}
}
Push notification sender:
import Foundation
class PushNotificationSender {
func sendPushNotification(to token: String, title: String) {
let urlString = "https://fcm.googleapis.com/fcm/send"
let url = NSURL(string: urlString)!
let paramString: [String : Any] = ["to" : token,
"notification" : ["title" : title],
"data" : ["user" : "test_id"]
]
let request = NSMutableURLRequest(url: url as URL)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject:paramString, options: [.prettyPrinted])
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("key=AAAA40aUkvQ:APA91bF-CnQ2x9eHtXyzBBymNvfy6YlFeN-uv8HVgSmX6Po7o8Ko3TEL7q4zwFPx8JiZnQI6pkYVIt2OlNevJr5-K-igKFB7439ssl_9lDm-6QKPLsLGfa0x3PsCCw5johTFh5UzcZe8", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
if let jsonData = data {
if let jsonDataDict = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject] {
NSLog("Received data:\n\(jsonDataDict))")
}
}
} catch let err as NSError {
print(err.debugDescription)
}
}
task.resume()
}
}
View controller:
...
let sender = PushNotificationSender()
sender.sendPushNotification(to: post_token!, title: "New notification!")
AppDelegate has a window property that contains all the view controllers that is being displayed.
There's a rootViewController property for the window object, which access the most bottom view controllers in the stack. The root view controller is our entry point to access view controllers from AppDelegate.
Say we want to show the ListViewController when user tap on push notification:
Just add storyboard id for the view controller in the storyboard
To jump to a particular view controller, you need to instantiate the destination view controller and push it
func jumpToViewController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// instantiate the view controller we want to show from storyboard
// root view controller is tab bar controller (initial view controller in storyboard)
// the selected tab is a navigation controller
// then we push the new view controller to it
if let listVC = storyboard.instantiateViewController(withIdentifier: "ListViewController") as? ListViewController,
let tabBarController = self.window?.rootViewController as? UITabBarController,
let navController = tabBarController.selectedViewController as? UINavigationController {
// we can modify properties of the new view controller using notification data
// (eg: title of notification)
listVC.title = response.notification.request.content.title
navController.pushViewController(listVC, animated: true)
}
}
You can check launchOptions to know weather the application has been launched from notification or not in didFinishLaunchingWithOptions method in appdelegate and move to your required viewcontroller accordingly.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
if launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] != nil {
// Move to your required viewcontroller here
}
}

Firebase push notifications not working with iOS

I want to implement push notification using Firebase Cloud Messaging
I have setup my project and uploaded APN certificate as explained
and I am sending Test messages using fcmtoken to my real device
my configuration is as follows in AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
registerForPushNotifications(app: application)
return true
}
func registerForPushNotifications(app: UIApplication) {
UNUserNotificationCenter.current().delegate = self
Messaging.messaging().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { (authorized, error) in
if let error = error {
print(error.localizedDescription)
return
}
if authorized {
print("authorized")
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
} else {
print("denied")
}
}
app.registerForRemoteNotifications()
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
func application(_ application: UIApplication,
didReceiveRemoteNotification notification: [AnyHashable : Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("notofication arrivied")
if Auth.auth().canHandleNotification(notification) {
completionHandler(.noData)
return
}
// This notification is not auth related, developer should handle it.
}
it is supposed to see notofication arrivied but it didn't Also set a beak point It seems this part is never being excused thus message is not coming
I don’t see this in your AppDelegate unless you have Swizzling enabled
func application(application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
Messaging.messaging().apnsToken = deviceToken
}
This code maps your APNs device token to FCM token, which is necessary because APNs token is the only way you can send a push notification.
func sendPushNotification(to token: String, title: String, body: String, userInfo: [String: Any]) {
let payload: [String: Any] = ["title": title, "body": body, "sound": "sound.caf"]
let paramString: [String: Any] = ["to": token, "notification": payload, "data": userInfo]
let urlString = "https://fcm.googleapis.com/fcm/send"
let url = NSURL(string: urlString)!
let request = NSMutableURLRequest(url: url as URL)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject:paramString, options: [.prettyPrinted])
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("key=\(Keys.gmsServerKey)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
if let data = data {
if let object = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: Any] {
NSLog("Received data: \(object))")
}
}
} catch let err as NSError {
print(err.debugDescription)
}
}
task.resume()
}

Push Notifications don't appear on devices

I have already written a lot of code in which I set notifications and try to send a notification from a user to another but now here is my problem, there isn't any notification that appears on the other user's device. The app doesn't crash and I have don't have build failed so I really don't know where I am wrong. Thanks in advance for your answers, here's my code...
// Parts about push notifications in AppDelegate.swift
static let NOTIFICATION_URL = "https://gcm-http.googleapis.com/gcm/send"
static var DEVICEID = String()
static let SERVERKEY = "AAAAuIcRiYI:APA91bHA8Q4IJBG9dG4RY9YOZ3v4MlcVZjmYs3XhiMY3xpQm3bTSjrINUiImaE0t17Y6mghR2vN1ezJTMSVFmHlgOUBX8KQZEckgwCkc1tlMdpm_UjxobmrHf3GbvwrKtHVZsJR-v1GG"
#available(iOS 10.0, *){
UNUserNotificationCenter.current().delegate = self
// Request Autorization.
let option : UNAuthorizationOptions = [.alert,.badge,.sound]
UNUserNotificationCenter.current().requestAuthorization(options: option) { (bool, err) in
}
}
else{
let settings : UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert,.badge,.sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
UIApplication.shared.applicationIconBadgeNumber = 0
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
guard let newToken = InstanceID.instanceID().token() else {return}
AppDelegate.DEVICEID = newToken
connectToFCM()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let notification = response.notification.request.content.body
print(notification)
completionHandler()
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
guard let token = InstanceID.instanceID().token() else {return}
AppDelegate.DEVICEID = token
print(token)
connectToFCM()
}
func connectToFCM()
{
Messaging.messaging().shouldEstablishDirectChannel = true
}
// Parts about notifications in messages.swift, I call this function after the user sends a message in way that the person who receives the message receives at the same time a notification.
fileprivate func setupPushNotification(fromDevice:String)
{
Database.database().reference().child("users").child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let device = value?["device"] as? String ?? ""
guard let message = self.authorText else {return}
let title = "You received a new message !"
let body = "from \(message)"
let toDeviceID = device
var headers:HTTPHeaders = HTTPHeaders()
headers = ["Content-Type":"application/json","Authorization":"key=\(AppDelegate.SERVERKEY)"
]
let notification = ["to":"\(toDeviceID)","notification":["body":body,"title":title,"badge":1,"sound":"default"]] as [String:Any]
Alamofire.request(AppDelegate.NOTIFICATION_URL as URLConvertible, method: .post as HTTPMethod, parameters: notification, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in
print(response)
}
})
}

Swift; HTTP request from UNUserNotificationCenter not working: The Internet connection appears to be offline

I'm using Xcode Version 9.4.1 (9F2000).
I have this code in AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
showPushButtons()
return true
}
func httpRequest(file: String, postKey1: String, postValue1: String, postKey2: String, postValue2: String) {
let url = URL(string: "https://www.example.com/\(file)")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let postString = "\(postKey1)=\(postValue1)&\(postKey2)=\(postValue2)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(String(describing: responseString))")
}
task.resume()
}
func showPushButtons(){
let replyAction = UNTextInputNotificationAction(
identifier: "reply.action",
title: "Reply to message",
textInputButtonTitle: "Send",
textInputPlaceholder: "Write some text here")
let pushNotificationButtons = UNNotificationCategory(
identifier: "allreply.action",
actions: [replyAction],
intentIdentifiers: [],
options: [])
UNUserNotificationCenter.current().setNotificationCategories([pushNotificationButtons])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "reply.action" {
if let textResponse = response as? UNTextInputNotificationResponse {
let sendText = textResponse.userText
print("Received text message: \(sendText)")
httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "Peter")
}
}
completionHandler()
}
What it does:
When receiving a push notification and making a force touch, a textfield and the keyboard will appear (as known from messaging apps like WhatsApp). You can write some text and submit/send it.
You can get and print that submitted message with this line:
print("Received text message: \(sendText)")
This is working without any problems.
But when trying to send the data to my server like this:
httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "David")
it's not working. There's no access to my server and I'm getting errors like this in console log:
Received text message: First try
2018-07-19 08:45:00.643935+0200 MyApp[4307:1502538] +[CATransaction
synchronize] called within transaction
2018-07-19 08:45:00.644639+0200 MyApp[4307:1502538] +[CATransaction
synchronize] called within transaction
2018-07-19 08:45:13.091958+0200 MyApp[4307:1502647] TIC TCP Conn
Failed [1:0x1c4169a80]: 1:50 Err(50)
2018-07-19 08:45:13.093089+0200 MyApp[4307:1502647] Task
<1E8151BB-7098-46CD-9F68-8AA0E320CB7D>.<1> HTTP load failed (error
code: -1009 [1:50])
Received text message: Second try
2018-07-19 08:45:13.094756+0200 MyApp[4307:1503029] Task
<1E8151BB-7098-46CD-9F68-8AA0E320CB7D>.<1> finished with error - code:
-1009
2018-07-19 08:45:13.096208+0200 MyApp[4307:1502538] +[CATransaction
synchronize] called within transaction
2018-07-19 08:45:13.096580+0200 MyApp[4307:1502538] +[CATransaction
synchronize] called within transaction error=Optional(Error
Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to
be offline." UserInfo={NSUnderlyingError=0x1cc047320 {Error
Domain=kCFErrorDomainCFNetwork Code=-1009 "(null)"
UserInfo={_kCFStreamErrorCodeKey=50, _kCFStreamErrorDomainKey=1}},
NSErrorFailingURLStringKey=https://www.example.com/message.php,
NSErrorFailingURLKey=https://www.example.com/message.php,
_kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=50, NSLocalizedDescription=The Internet connection appears to be
offline.})
My function httpRequest() seems to work because I can e.g. call it from didFinishLaunchingWithOptions like this:
httpRequest(file: "message.php", postKey1: "message", postValue1: "Hello!", postKey2: "chat_user", postValue2: "David")
without any problem. That also means that my domain and my server are working fine.
But why can't I call my httpRequest() function from my UNUserNotificationCenter function?
When receiving a push notification, my app is of course in background or closed. Do I need some special code to make it work in background mode or so?
Here is my working code from AppDelegate.swift:
// AppDelegate.swift
import UIKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
pushAction()
return true
}
func registerForPushNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
(granted, error) in
print("\nPermission granted: \(granted)\n")
self.pushAction()
guard granted else { return }
self.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
print("\nNotification settings: \(settings)\n")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async(execute: {
UIApplication.shared.registerForRemoteNotifications()
})
}
}
func httpRequest(file: String, postKey1: String, postValue1: String, postKey2: String, postValue2: String) {
let url = URL(string: "https://www.example.com/\(file)")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let postString = "\(postKey1)=\(postValue1)&\(postKey2)=\(postValue2)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("\nerror=\(String(describing: error))\n")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("\nstatusCode should be 200, but is \(httpStatus.statusCode)\n")
print("\nresponse = \(String(describing: response))\n")
}
let responseString = String(data: data, encoding: .utf8)
print("\nresponseString = \(String(describing: responseString))\n")
}
task.resume()
}
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let tokenParts = deviceToken.map { data -> String in
return String(format: "%02.2hhx", data)
}
let token = tokenParts.joined()
print("\nDevice Token: \(token)\n")
}
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("\nFailed to register: \(error)\n")
}
func pushAction(){
let replyAction = UNTextInputNotificationAction(
identifier: "reply.action",
title: "Reply to message",
options:[],
textInputButtonTitle: "Send",
textInputPlaceholder: "Input/write text here")
let pushNotificationButtons = UNNotificationCategory(
identifier: "allreply.action",
actions: [replyAction],
intentIdentifiers: [],
options: [])
UNUserNotificationCenter.current().setNotificationCategories([pushNotificationButtons])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
// If you don’t want to show notification when app is open, do something else here and make a return here.
// Even if you don’t implement this delegate method, you will not see the notification on the specified controller. So, you have to implement this delegate and make sure the below line execute. i.e. completionHandler.
completionHandler([.sound,.alert,.badge])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "reply.action" {
if let textResponse = response as? UNTextInputNotificationResponse {
if UIApplication.shared.applicationState != .active{
self.registerBackgroundTask()
}
let sendText = textResponse.userText
print("\nReceived text message: \(sendText)\n")
DispatchQueue.global(qos: .background).async {
self.httpRequest(file: "message.php", postKey1: "message", postValue1: sendText, postKey2: "user", postValue2: "Peter")
}
}
}
completionHandler()
}
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
print("\nBackground task ended.\n")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
}
Don't forget:
– Create a push certificate
– You'll see your device token in console log
– Add "category":"allreply.action" in your aps payload like this:
{
"aps":{
"alert":{
"title":"Hello",
"body":"This is a test!"
},
"badge":0,
"sound":"default",
"category":"allreply.action"
}
}
Enable Push Notifications and Background Modes in Capabilities:
Big thank you to Raza K. from Freelancer.com!

Push Notification with Image - iOS - Swift

Hi I just want to show push Notification with Image. Im using the below code and im not sure where im doing mistake it took me more than 3 weeks, I gone through many Links but still it couldn't be fixed. the below is my App delegate code
AppDelegate.Swift
import UIKit
import UserNotifications
var deviceTokenString:String = ""
var badgeCount = 0
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Push Notification
if #available(iOS 10.0, *)
{
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
// actions based on whether notifications were authorised or not
guard error == nil else {
//Display Error.. Handle Error.. etc..
return
}
if granted
{
//Do stuff here..
}
else {
//Handle user denying permissions..
}
}
application.registerForRemoteNotifications()
} else {
// Fallback on earlier versions
}
registerForRemoteNotification()
// iOS 10 support
if #available(iOS 10, *) {
UNUserNotificationCenter.current().requestAuthorization(options:[.alert, .sound]){ (granted, error) in }
application.registerForRemoteNotifications()
}
// iOS 9 support
else if #available(iOS 9, *) {
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.sound, .alert], categories: nil))
UIApplication.shared.registerForRemoteNotifications()
}
// iOS 8 support
else if #available(iOS 8, *) {
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.sound, .alert], categories: nil))
UIApplication.shared.registerForRemoteNotifications()
}
// iOS 7 support
else {
application.registerForRemoteNotifications(matching: [.sound, .alert])
}
return true
}
func registerForRemoteNotification() {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.delegate = self
center.requestAuthorization(options: [.sound, .alert]) { (granted, error) in
if error == nil{
UIApplication.shared.registerForRemoteNotifications()
// UIApplication.shared.applicationIconBadgeNumber = 5
}
}
}
else {
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.sound, .alert], categories: nil))
UIApplication.shared.registerForRemoteNotifications()
// UIApplication.shared.applicationIconBadgeNumber = 5
}
}
func incrementBadgeNumberBy(badgeNumberIncrement: Int)
{
let currentBadgeNumber = UIApplication.shared.applicationIconBadgeNumber
let updatedBadgeNumber = currentBadgeNumber + badgeNumberIncrement
if (updatedBadgeNumber > 0)
{
UIApplication.shared.applicationIconBadgeNumber = updatedBadgeNumber
}
else
{
UIApplication.shared.applicationIconBadgeNumber = 0
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Couldn't register: \(error)")
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
deviceTokenString = deviceToken.hexString()
// deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
print("device token: \(deviceTokenString)")
}
// Push notification received
func application(_ application: UIApplication, didReceiveRemoteNotification data: [AnyHashable : Any]) {
// Print notification payload data
badgeCount = badgeCount + 1
self.incrementBadgeNumberBy(badgeNumberIncrement: badgeCount)
print("Push notification received: \(data)")
}
// Notification will present call back
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .sound, .badge])
print("UserInfo: \(notification.request.content.userInfo)")
var userinfo = NSDictionary()
userinfo = notification.request.content.userInfo as NSDictionary
let imgData = userinfo.value(forKey: "data")! as! NSDictionary
let url = imgData.value(forKey: "attachment-url")
let imgUrl = URL(string: url as! String)!
// 1. Create Notification Content
let content = UNMutableNotificationContent()
// 2. Create Notification Attachment
URLSession.shared.downloadTask(with: imgUrl)
{(location, response, error) in
print("location: \(location!)")
if error == nil
{
if let location = location
{
// Move temporary file to remove .tmp extension
let tmpDirectory = NSTemporaryDirectory()
let tmpFile = "file://".appending(tmpDirectory).appending(imgUrl.lastPathComponent)
print("tmpFile: \(tmpFile)")
let tmpUrl = URL(string: tmpFile)!
print("tmpUrl: \(tmpUrl)")
try! FileManager.default.moveItem(at: location, to: tmpUrl)
// Add the attachment to the notification content
if let attachment = try? UNNotificationAttachment(identifier: "attachment", url: tmpUrl) {
content.attachments = [attachment]
print("attachment: \(content.attachments)")
// 3. Create Notification Request
let request = UNNotificationRequest.init(identifier: String.UNNotificationRequest.NormalLocalPush.rawValue,
content: content, trigger: nil)
content.title = "\(userinfo.value(forKeyPath: "aps.alert.title")!)"
content.body = "\(userinfo.value(forKeyPath: "aps.alert.body")!)"
content.sound = UNNotificationSound.default()
content.badge = (UIApplication.shared.applicationIconBadgeNumber + 1) as NSNumber;
content.categoryIdentifier = String.UNNotificationCategory.Normal.rawValue
// 4. Add to NotificationCenter
let center = UNUserNotificationCenter.current()
center.add(request)
}
}
}
else
{
print("Error: \(error!)")
}
}.resume()
}
#available(iOS 10.0, *)
// Notification interaction response call back
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
print("\(response.notification.request.content.userInfo)")
var userinfo = NSDictionary()
userinfo = response.notification.request.content.userInfo as NSDictionary
let imgData = userinfo.value(forKey: "data")! as! NSDictionary
let url = imgData.value(forKey: "attachment-url")
let imgUrl = URL(string: url as! String)!
// 1. Create Notification Content
let content = UNMutableNotificationContent()
content.title = "\(userinfo.value(forKeyPath: "aps.alert.title")!)"
content.body = "\(userinfo.value(forKeyPath: "aps.alert.body")!)"
content.sound = UNNotificationSound.default()
content.badge = (UIApplication.shared.applicationIconBadgeNumber + 1) as NSNumber;
content.categoryIdentifier = String.UNNotificationCategory.Normal.rawValue // 设置通知类型标示
// 2. Create Notification Attachment
URLSession.shared.downloadTask(with: imgUrl) { (location, response, error) in
if let location = location {
// Move temporary file to remove .tmp extension
let tmpDirectory = NSTemporaryDirectory()
let tmpFile = "file://".appending(tmpDirectory).appending(imgUrl.lastPathComponent)
let tmpUrl = URL(string: tmpFile)!
try! FileManager.default.moveItem(at: location, to: tmpUrl)
// Add the attachment to the notification content
if let attachment = try? UNNotificationAttachment(identifier: "", url: tmpUrl) {
content.attachments = [attachment]
}
}
// Serve the notification content
// self.contentHandler!(content)
}.resume()
// if let attachement = try? UNNotificationAttachment(identifier: "attachment", url: imgUrl, options: nil)
// {
// content.attachments = [attachement]
// }
// 3. Create Notification Request
let request = UNNotificationRequest.init(identifier: String.UNNotificationRequest.NormalLocalPush.rawValue,
content: content, trigger: nil)
// 4. Add to NotificationCenter
let center = UNUserNotificationCenter.current()
center.add(request)
let responseNotificationRequestIdentifier = response.notification.request.identifier
if responseNotificationRequestIdentifier == String.UNNotificationRequest.NormalLocalPush.rawValue ||
responseNotificationRequestIdentifier == String.UNNotificationRequest.LocalPushWithTrigger.rawValue ||
responseNotificationRequestIdentifier == String.UNNotificationRequest.LocalPushWithCustomUI1.rawValue ||
responseNotificationRequestIdentifier == String.UNNotificationRequest.LocalPushWithCustomUI2.rawValue {
let actionIdentifier = response.actionIdentifier
switch actionIdentifier {
case String.UNNotificationAction.Accept.rawValue:
break
case String.UNNotificationAction.Reject.rawValue:
break
case String.UNNotificationAction.Input.rawValue:
break
case UNNotificationDismissActionIdentifier:
break
case UNNotificationDefaultActionIdentifier:
break
default:
break
}
}
completionHandler();
}
}
extension Data
{
func hexString() -> String
{
return self.reduce("") { string, byte in
string + String(format: "%02X", byte)
}
}
}
And below is my Extension Code which im using for custom Push notification,
Extension.swift
import Foundation
extension String {
enum UNNotificationAction : String {
case Accept
case Reject
case Input
}
enum UNNotificationCategory : String {
case Normal
case Cheer
case CheerText
}
enum UNNotificationRequest : String {
case NormalLocalPush
case LocalPushWithTrigger
case LocalPushWithCustomUI1
case LocalPushWithCustomUI2
}
}
extension URL {
enum ResourceType : String {
case Local
case Local1
case Remote
case AttachmentRemote
}
static func resource(type :ResourceType) -> URL
{
switch type {
case .Local:
return Bundle.main.url(forResource: "cheer", withExtension: "png")!
case .Local1:
return Bundle.main.url(forResource: "hahaha", withExtension: "gif")!
case .Remote:
return URL(string: "http://ww1.sinaimg.cn/large/65312d9agw1f59leskkcij20cs0csmym.jpg")!
case .AttachmentRemote:
return URL(string: "https://assets-cdn.github.com/images/modules/open_graph/github-mark.png")!
}
}
}
extension URLSession {
class func downloadImage(atURL url: URL, withCompletionHandler completionHandler: #escaping (Data?, NSError?) -> Void) {
let dataTask = URLSession.shared.dataTask(with: url) { (data: Data?, response: URLResponse?, error: Error?) in
completionHandler(data, error as NSError?)
}
dataTask.resume()
}
}
and My Api response is,
[AnyHashable("aps"):
{
alert = {
body = test;
title = "N-Gal";
};
"mutable-content" = 1;
sound = default;
},
AnyHashable("data"):
{
"attachment-url" = "https://www.n-gal.com/image/cache/catalog/HomeBanner/Banners/1172X450-N-Gal-Footwear-Banner-100x100.jpg";
}]
This code is based on the tutorial https://github.com/maquannene/UserNotifications. Please give me a solution to fix this... Thanks in Advance...!
From your code snippet I conclude you are talking about remote notifications. That's an important distinction. If you want to 'enrich' a remote notification (e.g. add an image), you need a UNNotificationServiceExtension:
For local notifications, the app adds attachments when creating the
rest of the notification’s content. To add attachments to a remote
notification, use a notification service extension to modify the
notification content before it is delivered. For more information
about implementing a notification service extension, see
UNNotificationServiceExtension
Source: Apple documentation. (emphasis mine)
That extension lives outside of your app and is called before the user gets to see the remote notification. That way you have the chance to load all of your remote resources before the notification is scheduled for delivery. For more info on the lifecycle of extensions and how they communicate with their host app, take a look at the App Extension Programming Guide.
To add the extension in Xcode, go to File > New > Target and select a Notification Service Extension:
This will create new extension target and embed it in the host target:
In the NotificationService.swift file you will find the entry point where you can start customising the notification content.
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
Be sure to take look at the UNNotificationServiceExtension class overview for more details.

Resources