How to run app with UNNotificationServiceExtension on pre iOS 10? - ios

My app implements the new iOS 10 rich push NotificationService extension.
Everything works as expected on iOS 10, but I also want to support pre iOS 10 devices - of course not rich push, but just regular push. When lowering the deployment target in Xcode to e.g. 8.0 or 9.0 and trying to run on an older simulator or device i get the following errors:
Simulator: The operation couldn’t be completed. (LaunchServicesError error 0.)
Device: This app contains an app extension that specifies an extension point identifier that is not supported on this version of iOS for the value of the NSExtensionPointIdentifier key in its Info.plist.
I couldn't find anything officially by Apple stating that your app will only run on iOS 10+ once you add a Service Extension - can someone confirm that?

Bhavuk Jain is talking about how to support notification on older ios but doesn't solve the LaunchServicesError. To solve this you need to go to your extension target -> General -> Set Deployment Target (10.0 for this case) under Deployment Info.

Firstly Initialize the notification services:
func initializeNotificationServices() -> Void {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.delegate = self
center.requestAuthorization(options: [.sound, .alert, .badge]) { (granted, error) in
if granted {
UIApplication.shared.registerForRemoteNotifications()
}
}
}else {
let settings = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
}
If successfully registered for remote notifications, this will be called for all the devices:
optional public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
For iOS 10 only, to handle remote notifications:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
notificationReceived(userInfo: userInfo, application: nil)
}
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
}
For devices below iOS 10:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
}

change status of frameworks to optional . when it is required some frameworks not work by ios 9.
enter image description here

Related

Unable to make push notifications works on a SwiftUI app with Firebase cloud Messaging

I'm trying to implement push notifications with Firebase Cloud Messaging on an iOS application.
My applications uses the SwiftUI life cycle.
I followed the official documentation from Firebase documentation
But, for the moment, I'm unable to make it works. I'try a lot of things, but none of them make the job. There is somebody who sees what can be the problem?
I tested on a real device, which ask me for the notification's permission. But no one arrives...
My AppDelegate class:
import SwiftUI
import FirebaseCore
import GoogleMobileAds
import FirebaseMessaging
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
Messaging.messaging().delegate = self
GADMobileAds.sharedInstance().start(completionHandler: nil)
return true
}
}
extension AppDelegate: UNUserNotificationCenterDelegate{
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
Messaging.messaging().appDidReceiveMessage(userInfo)
// Change this to your preferred presentation option
completionHandler([[.alert, .sound]])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
Messaging.messaging().appDidReceiveMessage(userInfo)
completionHandler()
}
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
Messaging.messaging().appDidReceiveMessage(userInfo)
completionHandler(.noData)
}
func application(application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
}
extension AppDelegate: MessagingDelegate{
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
print("Firebase registration token: \(String(describing: 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.
}
}
#main
struct BochoGameApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
SplashScreen()
}
}
}
I created a key on my Apple account with the Apple Push Notifications service (APNs) service enabled. After I configured the Firebase console with it.
(I erased the empty fields for the image)
I set the next capabilities
On the info.pils I set FirebaseAppDelegateProxyEnabled to false
Also, I have an App ID with the Push Notification checked
In my profile says I enable to use Push notifications.
And I set the provisioning profile on Xcode
Any idea? Any help will be appreciated
Finally, I found a solution. The official documentation suggest changing the methods if you disable swizzling, or you are building a SwiftUI application. I don't know why they suggest that, because if you keep the original methods, works....
I follow this tutorial and now push notifications arrive to my device.Excellent tutorial
Okay, now I realize that with the methods of this post works also. You only have to edit that.
completionHandler([[.banner,.badge ,.sound]]).

Not receiving remote push notifications on iOS

I'm currently rewriting my app from Objective-C to Swift and working on notifications. For some reason Swift version of the app is not receiving any remote push notifications while Objective-C version does.
Here's the code I'm using to register for notifications in AppDelegate:
UNUserNotificationCenter.current().delegate = self
let options: UNAuthorizationOptions = [.badge, .alert, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: options, completionHandler: { granted, error in
print("access granted: \(granted)")
})
application.registerForRemoteNotifications()
I assume that the app successfully registers because didRegisterForRemoteNotificationsWithDeviceToken method gets called. But when I try to send the test notification using the token I got from that method, I don't get actual notification on device.
Also none of these methods get called:
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
}
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (_ options: UNNotificationPresentationOptions) -> Void) {
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
}
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, forRemoteNotification userInfo: [AnyHashable : Any], completionHandler: #escaping () -> Void) {
}
What am I doing wrong?
you say that you are not receiving so let's first make sure that those methods you mention before that are not being called are comming from here extension AppDelegate: UNUserNotificationCenterDelegate (By the way when you do implement the didReceive method make sure you call the completionHandler() at the end)
When you do this:
application.registerForRemoteNotifications()
Make sure to run it from the main thread, as it can be called if not specified from the background and that might fail. You can do so by doing
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
I'll assume that you are getting the device token this way or something similar:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// 1. Convert device token to string
let tokenParts = deviceToken.map { data -> String in
return String(format: "%02.2hhx", data)
}
let token = tokenParts.joined()
// 2. Print device token to use for PNs payloads
print("Device Token: \(token)")
}
Finally implement this method to see if there are any errors while registering the device
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
// 1. Print out error if PNs registration not successful
print("Failed to register for remote notifications with error: \(error)")
}
Oh by the way (just in case you have missed it) make sure you enabled in your project's target the push notification.
Make sure you have enabled Push notification in your project setting capabilities.
How to enable:
go to target: -> select project -> go to capabilities -> go to Push Notification. enable it there.
Another thing is the certificate. While development you should not Production certificates.

IOS: Inapp notification + Notification Service + Content extensions

The thing is that notification service extension is useless by itself, except one thing that it can be shown when the app is killed. So with inapp notifications and content extension I can show custom notification, but that notification will appear only if the app is not killed/force-closed.
Question: How to manage the inapp notifications in case there are content and service notifications extensions, and how to force notifications service extension to call/wake up the notification content extension.
Seemed that I needed to clean the project and remove the app and install again. Here is the full steps for FCM to achieve this case, maybe some of the steps are redundant, but I don't want to touch it while it is working :
App is killed/closed: Notification appears with custom content view
App is in background: Notification appears with custom content view
App is in foreground: Notification arrives silently
From server side the notification should look like this
{
"notification"://mandatory
{
"data":{},
"body":""//seemed mandatory as well, anyway you can change it in service extension
}
"content_available":true,//mandatory
"mutable_content":true,//mandatory
"to":""//mandatory
}
Create Notification Service Extension target.
In info.plist under NSExtension add UNNotificationExtensionCategory as Array and add one category with whatever name you want, but use the same one everywhere.
In Notification Service Extension target be sure everything is same as in main target(swift version, build version, deployment target, devices).
In capabilities add app groups(should be same as in main target).
In NotificationService in didReceive get the mutable content
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
Add your category to the content
bestAttemptContent.categoryIdentifier = "yourCategory"
After calling contentHandler(bestAttemptContent) the standart notification appears.
Create Notification Content Extension target.
In info.plist under NSExtension add UNNotificationExtensionCategory as Array and add one category with same name you already created for the service.
Change the design in MainInterface.storyboard according to your design.
In NotificationViewController the custom data will be in notification.request.content.userInfo["gcm.notification.data"] . So in NotificationViewController fill the views with the data. Don't forget about the the preferredContentSize.
In capabilities add app groups(should be same as in main target).
In main target AppDelegate implement UNUserNotificationCenterDelegate.
In didFinishLaunchingWithOptions add
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
}
application.registerForRemoteNotifications()
In AppDelegate add
func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken
deviceToken: Data ) {
Messaging.messaging().apnsToken = deviceToken
}
In userNotificationCenter willPresent check if your app is not running. I made it this way:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
if self.window?.rootViewController == nil {
completionHandler([.badge, .alert, .sound])
}
UIApplication.shared.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
}
Handle the silent notification here:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
Handle the tap on the notification in here:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void)
here you can get the custom data with the same way response.notification.request.content.userInfo["gcm.notification.data"]
If you have async network calls to load data/images don't forget to implement func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void)
Seems that's all :) . Ah, the extensions are working starting IOS 10. Please correct me if there is something missing or something is redundant.

iOS push notifications - didReceiveRemoteNotification:fetchCompletionHandler: never called in foreground

I have implemented remote notifications in my app, however I have problems with didReceiveRemoteNotification:fetchCompletionHandler:.
When the phone's screen is locked, I am getting the notification. If the user swipes the notification, app comes back to foreground and didReceiveRemoteNotification:fetchCompletionHandler: is called.
But if the app is running in the foreground, didReceiveRemoteNotification:fetchCompletionHandler: is never called. What might be the reason?
I'm working on iOS 10.3.2
Which iOS version you are working on ?
There are different methods for iOS 9 and iOS 10.
Below are the ones for iOS 10
//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) {
}
//Called to let your app know which action was selected by the user for a given notification.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
}
For iOS 9, make sure to register your notification in didBecomeActive
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil))
UIApplication.shared.registerForRemoteNotifications()
and in your didReceiveRemoteNotification
func application(_ application: UIApplication, didReceiveRemoteNotification data: [AnyHashable : Any]) {
if application.applicationState == .active {
//foreground state
}
}
Best approach would be to put a debugger in didReceiveRemoteNotification and check further.

How to enable/disable push notification from my app

I'm working on App that have push notification property. And I should enable/disable the push notification permission within my app without go to iPhone settings.
Is there a way to implement that?
I searched a lot, but I didn't find any proper way to implement it.
Any help?
If a user denied permissions for push notifications you can not let him enable it from within the app.
You could however, set a button in your settings app (ViewController), and let the user switch the notifications off and on there. Then you can set a boolean to check before sending notifications. This way a user might use it instead of disabling the app's notification permission on the device settings.
Enable Push Notification (Setup from app):
if #available(iOS 10.0, *) {
// SETUP FOR NOTIFICATION FOR iOS >= 10.0
let center = UNUserNotificationCenter.current()
center.delegate = self
center.requestAuthorization(options: [.sound, .alert, .badge]) { (granted, error) in
if error == nil{
DispatchQueue.main.async(execute: {
UIApplication.shared.registerForRemoteNotifications()
})
}
}
}else{
// SETUP FOR NOTIFICATION FOR iOS < 10.0
let settings = UIUserNotificationSettings(types: [.sound, .alert, .badge], categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
// This is an asynchronous method to retrieve a Device Token
// Callbacks are in AppDelegate.swift
// Success = didRegisterForRemoteNotificationsWithDeviceToken
// Fail = didFailToRegisterForRemoteNotificationsWithError
UIApplication.shared.registerForRemoteNotifications()
}
Delegate methods to handle push notifications
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
}
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// ...register device token with our Time Entry API server via REST
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
//print("DidFaildRegistration : Device token for push notifications: FAIL -- ")
//print(error.localizedDescription)
}
Disable Push Notifiacation:
UIApplication.shared.unregisterForRemoteNotifications()

Resources