I'm writing a Swift app with CloudKit. When a record is modified in CloudKit by a device, I want the corresponding records to be updated in the local storage of the other devices without displaying a push notification.
Do I need to call registerUserNotificationSettings in didFinishLaunchingWithOptions (meaning that the user has to accept the notifications for my app) even if I don't plan to display any push notification?
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Alert, categories: nil))
In this case you do not need to call registerUserNotificationSettings.
You need to add the Info.plist setting "Required background mode" (UIBackgroundModes), "App downloads content in response to push notifications" (remote-notification). And also call registerForRemoteNotifications. Finally, set notificationInfo.shouldSendContentAvailable = YES; on your subscription.
Now since your app is being run to respond to all notifications you need to be careful to handle the case where a notification is missed, you can use airplane mode to test that, only the last is delivered.
Note, once you have created your subscription from any device, application:didReceiveRemoteNotification:fetchCompletionHandler: will be called on all devices that are using the same iCloud account and have the app installed.
Yes you do need to call registerUserNotificationSettings even all you need is background remote notification. So user will be prompt for notifications permission. It makes no sense as users will not be seeing the notifications but that's how it is.
I use this to set it up:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let settings = UIUserNotificationSettings(forTypes: .None , categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
Make sure when you call CloudKit saveSubscription you provide shouldSendContentAvailable = true. The following code is for subscription for a custom zone:
let subscription = CKSubscription(zoneID:zoneID, options: CKSubscriptionOptions(rawValue: 0))
let notificationInfo = CKNotificationInfo()
notificationInfo.shouldSendContentAvailable = true
subscription.notificationInfo = notificationInfo
CKContainer.defaultContainer().privateCloudDatabase.saveSubscription(subscription) { subscription, error in
}
You also need to enable Background Modes capability under Xcode for your project, and tick the box Remote Notifications.
User can go to Settings app to disable notifications for your app. But you will still receive remote notification trigger by CloudKit server.
Implement the following functions in your AppDelegate to receive remote notifications:
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {}
Related
I'm developing an Swift iOS 14 app that send and recieves push notifications from Firebase Cloud Messaging.
From FCM I send a message with a payload that must be treated by the app, updating an internal SQLite database with the payload data for later, show items in a view.
When the app is in Foreground, I recieved the notification in the didReceiveRemoteNotification method and update the database but when the app is in Background or killed, the notification is recieved but no one method is called to handle the payload and update de database.
I've read many topics about this problem but in none have I come to find a solution.
At the moment I don't want to use an external database to insert the data, and later read the external database, but if there is no other options i will change the app (the reason is that i don't want to store any information out of the user application).
My AppDelegate.swift is the following:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Firebase Auth + APNs
FirebaseApp.configure()
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
application.registerForRemoteNotifications()
Messaging.messaging().delegate = self
return true
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
UserDefaults.standard.set(fcmToken, forKey: UserConstants.MESSAGING_TOKEN)
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if(userInfo["name"] != nil) {
ContactsService.sharedInstance.addAlert(phoneNumber: userInfo["phone"] as! String, name: userInfo["name"] as! String, isLocalized: Bool(userInfo["isLocalized"] as? String), longitude: (userInfo["longitude"] as! NSString).doubleValue, latitude: (userInfo["latitude"] as! NSString).doubleValue)
}
}
Can someone help me, telling me if it's possible to do in that way or it's necessary to store the data externally to later retrieve it?
Thank you!
There are two types of push notifications, alert notifications and background notifications. Alert notifications allow you to deliver visible alerts that can be interacted with in ways that your app can customize.Background notifications allow your application to fetch data from the background, upon receiving push notifications. Background notification should be used to keep your application up to date, even if the application isn't running. Also as of ios 10(and above)instead of using the didReceiveRemoteNotification method you can use didReceive method for handling the alert notifications.
Now coming back to your question in case of the alert notification the didReceive/didReceiveRemoteNotification method is called when the application is in the foreground or when the user taps on the application. Since, you want to update the database you can use the background notifications instead of the alert notification as it will automatically raise your application even when it is in background and will also call the didReceiveRemoteNotification:fetchCompletionHandler. while sending a background push notification make sure you :
Edit Info.plist and check the "Enable Background Modes" and "Remote notifications" check boxes.
Add "content-available":1 to your push notification payload, otherwise the app won't be woken if it's in the background
The notification’s POST request should contain the apns-push-type header field with a value of background, and the apns-priority field with a value of 5.
For more info please refer :
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app
I have been battling with this for a while now but I've finally succumbed to submitting a question to see if anyone else has had an issue.
Essentially I'm trying to use the Facebook Analytics push service and only seem to be receiving push notifications some of the time. I've been intercepting the API calls using charles proxy to ensure that push tokens are being sent correctly by the Facebook SDK and events are being logged as expected. From this perspective everything is fine.
Next I use the 'Push campaign setup verification' on the settings page to try and send push and in-app push tests, and these also work fine (cards are displayed correctly on the devices etc.) on the 3 test devices I'm using.
The problem arrises when trying to create a campaign to send pushes to a certain segment of the devices. The first device I tried with worked with no issues at all, but then subsequent devices (with no changes to the codebase at all) wouldn't receive any pushes from any campaigns that I set up. My target audience is 'Device OS is iOS' so I would expect that all iOS devices would receive the push from the campaign. This seemed a bit odd, so I deleted the app off all the devices and rebuilt them again (ensuring that the test push worked on all devices) and setup the campaign again, but this time even the first device no longer worked. In the events debugger I get a 'Push Notification Error' and an error_message of 'InvalidDeviceOS' which makes no sense at all.
I've been going back and fourth with this for a few hours now with no success. I can see the tokens being sent to Facebook, and I can see the events being logged in the event debugger, I can use the push test service in settings and all devices receive pushes without an issue, but as soon as I try to use a campaign I get nothing.
For completion purposes here are the snippets of code I'm using:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// ...
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil))
// ...
}
func application(application: UIApplication, didRegisterUserNotificationSettings settings: UIUserNotificationSettings) {
UIApplication.sharedApplication().registerForRemoteNotifications()
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
FBSDKAppEvents.setPushNotificationsDeviceToken(deviceToken)
}
Finally here is the events debugger after opening a fresh install on 3 different devices (see the first launch events, 2 app launches have ID's and 1 doesn't as it's not logged in yet).
Any help with this would be great, thanks.
UPDATE: I created a brand new app and a brand new Facebook analytics instance, new push certificates etc. Initially I started receiving pushes from campaigns, but once I deleted and reinstalled the app the campaign messaging started to fail again. I then installed it onto a different device and cloned the existing campaign and tried again. This then resulted in the original device getting the push but the new device didn't... the mind boggles!
Now I don't know whats going on. On the face of it the Facebook service seems to get flaky once a push error comes back from APNS, but it's really difficult to prove that assumption since the error reporting in the Event Debugger is next to useless.
Again for completeness here is the code from the new app:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil))
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
FBSDKAppEvents.activateApp()
}
// MARK: Notification Methods
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
application.registerForRemoteNotifications()
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
FBSDKAppEvents.setPushNotificationsDeviceToken(deviceToken)
FBSDKAppEvents.setUserID(NSUUID().uuidString)
var token: String = ""
for i in 0 ..< deviceToken.count {
token += String(format: "%02.2hhx", deviceToken[i] as CVarArg)
}
print("Token: \(token)")
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if FBNotificationsManager.shared().canPresentPushCard(fromRemoteNotificationPayload: userInfo) {
FBNotificationsManager.shared().presentPushCard(forRemoteNotificationPayload: userInfo, from: nil, completion: nil)
} else {
print("Unknown Payload")
}
completionHandler(.newData)
}
UPDATE 2: After hours of experimenting and digging we came across this bug report on Facebook's support pages which seems to confirm this is not an isolated issue: https://developers.facebook.com/bugs/118059088679219/
I have integrated push notification through GCM everything is working fine. But I am not getting notification message and sound. And the function didReceiveNotification: called in app delegate. And also not getting in background state.
Before making any comment or downvote consider following things.
I assume you have configured App Identifier in Developer portal, if not visit Apple Developer center
You have generated required provisional Profile & Certificate from Apple Developer Portal. If not visit App Distribution Guide
Make sure you have configured your bundle identifier correctly as defined in Apple Developer portal.
Following answer guides to configure APNS using your custom backend to send Push Notifications not for FireBase/GCM. To configure it using Firebase or GCM(As Firebase Cloud Messaging (FCM) is the new version of GCM) follow Google documentation
If all the above things are configured correctly then follow below steps:
Step 1: Register for APNS with Appropriate settings in didFinishLaunchingWithOptions inside AppDelegate file
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let notificationTypes: UIUserNotificationType = [UIUserNotificationType.Alert, UIUserNotificationType.Badge, UIUserNotificationType.Sound]
let pushNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
application.registerUserNotificationSettings(pushNotificationSettings)
application.registerForRemoteNotifications()
return true
}
Step 2: Add delegate methods to handle success or failure for APNS registration by adding following delegate methods
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
// Convert binary Device Token to a String (and remove the <,> and white space charaters).
var deviceTokenStr = deviceToken.description.stringByReplacingOccurrencesOfString(">", withString: "", options: nil, range: nil)
deviceTokenStr = deviceTokenStr.stringByReplacingOccurrencesOfString("<", withString: "", options: nil, range: nil)
deviceTokenStr = deviceTokenStr.stringByReplacingOccurrencesOfString(" ", withString: "", options: nil, range: nil)
print(deviceTokenStr);
// *** Store device token in your backend server to send Push Notification ***
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
print(error)
}
Step 3: Now you have configured your APNS on device end, You can fire Push Notification from your server/backend, When Push Notification is received following method will be called when your app is in Foreground. Implement it into AppDelegate.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
print(userInfo)
}
To handle Push Notification while your application is in background (but not killed by removing from multitask) you need to take care of following things.
Make sure you have enabled Background Modes in Project Navigation->Targets->Capabilities->Turn on Background Modes and select Remote Notifications.
Now implement following method to handle Push Notification while in background. Make sure you handle UIBackgroundFetchResult properly.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
}
Note: If func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) method is implemented func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) method will not be called.
Read more about APNS in Apple Documentation.
Usually, iOS apps can receive push notifications via APNS not GCM and could not get any data when app is in background state. If iOS app gets push notification via APNS and it is in background state, the push notifications just shown in notification center & top of the screen with app's icon. If you see the notification, there's no problem with the server.
And there's no data arrived when app is in the background state, you should make your server api for the notifications data when the app is back on foreground state.
Problem: I have Iphone 6s 64GB, running iOS 8.3, and my app doesn't show in the notification center. I tried all previous suggestions of deleting the apps, turn phone off, then on, wait 5 minutes, reinstall app thru Xcode, launch the app again, and still my app does not show in notification center.
BTW, I verified that if my app is running in foreground, I could process remote notification correctly.
Does anyone have this issue. Thanks.
This is my code in AppDelegate.swift.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
var type = UIUserNotificationType.Badge | UIUserNotificationType.Alert | UIUserNotificationType.Sound
var setting = UIUserNotificationSettings(forTypes: type, categories: nil)
UIApplication.sharedApplication().registerForRemoteNotifications()
println("..... application didFinishLaunchingWithOptions()")
if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary {
// there is a notification...do stuff...
println("dinFInishLaunchingWithOption().. calling didREceiveRemoteNotification")
self.application(application, didReceiveRemoteNotification: remoteNotification as [NSObject : AnyObject])
}
return true
}
I believe you have a small issue with your code
var type = UIUserNotificationType.Badge | UIUserNotificationType.Alert | UIUserNotificationType.Sound
var setting = UIUserNotificationSettings(forTypes: type, categories: nil)
UIApplication.sharedApplication().registerForRemoteNotifications()
Don't you need to provide registerForRemoteNotifications with the notifications you want to register with?
If you look at the documentation for registerForRemoteNotifications, it states you need to use registerUserNotificationSettings: first.
// Calling this will result in either
// application:didRegisterForRemoteNotificationsWithDeviceToken: or
// application:didFailToRegisterForRemoteNotificationsWithError: to be
// called on the application delegate. Note: these callbacks will be
// made only if the application has successfully registered for user
// notifications with registerUserNotificationSettings:, or if it is
// enabled for Background App Refresh.
#availability(iOS, introduced=8.0)
func registerForRemoteNotifications()
I think this is what you should do:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let notificationSettings = UIUserNotificationSettings(forTypes: .Badge | .Alert | .Sound, categories: nil)
application.registerUserNotificationSettings(notificationSettings)
application.registerForRemoteNotifications()
// Rest of your setup code...
return true
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
println("Did Register For Remote Notification")
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
println("Failed to Register For Remote Notifications: \(error.localizedDescription)")
}
This information might help if you change the types of registered notifications:
So iOS stores notification settings for 24 hours after the app is deleted. You could attempt to delete the app wait at least 24 hours then reinstall the app. Then open the App and say Yes to allowing Notifications you can then navigate to your setting and see the notifications settings.
Then only other way I know of to reset notifications settings without waiting for the 24 hour period is wiping the device and not restoring it from a backup.
Once your done with your test then you can restore it from the backup.
Alternatively you could change your app's bundle identifier when testing. It should cause iOS to think of it as a different app.
I have set up Parse.com's Push feature as follows, but I run into an issue when I try to send push notifications:
Certificates have been set up, signed, uploaded, etc.
SDK has been imported as per the instructions here
AppDelegate.swift has been edited to include the following:
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
Parse.setApplicationId("MY KEY. I KNOW IT IS CORRECT", clientKey: "MY CLIENT KEY. ALSO VERIFIED TO BE CORRECT")
let userNotificationTypes = (UIUserNotificationType.Alert |
UIUserNotificationType.Badge |
UIUserNotificationType.Sound);
let settings = UIUserNotificationSettings(forTypes: userNotificationTypes, categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
return true
}
func application(application: UIApplication!, didReceiveRemoteNotification userInfo:NSDictionary!) {
PFPush.handlePush(userInfo)
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let installation = PFInstallation.currentInstallation()
installation.setDeviceTokenFromData(deviceToken)
installation.saveInBackground()
}
However, when I try to send a push notification by selecting my app in the Parse dashboard, selecting "Push", and selecting " + Sends A Push", I get an error
No push notifications to display yet
You may need to configure push notifications for your app.
I had this same issue, and hammered my head against the wall for a while trying to figure it out until I realized that when I went to the Push Notifications tab in Parse, it somehow switched me into another earlier app that I had set up. Make sure you have the right app selected in the upper left corner. Hopefully your solution is as simple as mine was.