Pushwoosh IOS notifications, onOpen invoked without click - ios

I have an IOS native app with Pushwoosh notifications.
When I put the app in background mode (Not killed), and then received a push, methon onMessageOpened called, even the user didn’t open the push.
My onMessageOpened method:
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, PWMessagingDelegate {
...
...
func pushwoosh(_ pushwoosh: Pushwoosh!, onMessageOpened message: PWMessage!) {
var url = message.payload["url"] as! String
let myURL = URL(string:url)
let myRequest = URLRequest(url: myURL!)
self.webView.load(myRequest)
}
}
This is in my AppDelegate:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
Pushwoosh.sharedInstance()?.handlePushReceived(userInfo)
completionHandler(.noData)
}
In addition I have a difficult with debugging this issue. Because I received push only in production version. Not in debug mode.
EDIT:
After I add a log calls, I can see it called to onMessageOpened just after receiving a push. You can see the low latency between records.
This is my new code:
func pushwoosh(_ pushwoosh: Pushwoosh!, onMessageReceived message: PWMessage!) {
NSLog("PushWoosh: message recevied!")
}
func pushwoosh(_ pushwoosh: Pushwoosh!, onMessageOpened message: PWMessage!) {
NSLog("PushWoosh: message opened!")
...
}
And this is my logs after a few hours of device was untouched.

Related

How to subscribe to changes for a public database in CloudKit?

What is the best way to subscribe to a public database in CloudKit?
I have a table with persons. Every person contains a name and a location.
Once the location changes, the location is updated in CloudKit.
That part is working fine.
But I am not able to make it work to get a notification when there is a record update.
Some example would be really helpful, as I have looked into the possible option already.
I have looked into the options where I save the subscription in the database and also the CKModifySubscriptionsOperation option.
Currently, my code to subscribe looks like this:
let predicate = NSPredicate(format: "TRUEPREDICATE")
let newSubscription = CKQuerySubscription(recordType: "Person", predicate: predicate, options: [.firesOnRecordCreation, .firesOnRecordDeletion, .firesOnRecordUpdate])
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
newSubscription.notificationInfo = info
database.save(newSubscription, completionHandler: {
(subscription, error) in
if error != nil {
print("Error Creating Subscription")
print(error)
} else {
userSettings.set(true, forKey: "subscriptionSaved")
}
})
Can someone also show me how my AppDelegate should look like?
I have added the didReceiveRemoteNotification function to my AppDelegate. I also called application.registerForRemoteNotifications(). This is how my didReceiveRemoteNotification function looks like:
The print is not even coming for me.
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Notification!!")
let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) as? CKDatabaseNotification
if notification != nil {
AppData.checkUpdates(finishClosure: {(result) in
OperationQueue.main.addOperation {
completionHandler(result)
}
})
}
}
Here are a few other things you can check:
= 1 =
Make sure the CloudKit container defined in your code is the same one you are accessing in the CloudKit dashboard. Sometimes we overlook what we selected in Xcode as the CloudKit container when we create and test multiple containers.
= 2 =
Check the Subscriptions tab in the CloudKit dashboard and make sure your Person subscription is being created when you launch your app. If you see it, try deleting it in the CK Dashboard and then run your app again and make sure it shows up again.
= 3 =
Check the logs in the CK Dashboard. They will show a log entry of type push whenever a push notification is sent. If it's logging it when you update/add a record in the CK Dashboard, then you know the issue lies with your device.
= 4 =
Remember that push notifications don't work in the iOS simulator. You need an actual device (or a Mac if you are making a macOS app).
= 5 =
Through extensive testing, I've found notifications are more reliable if you always set the alertBody even if it's blank. Like this:
let info = CKSubscription.NotificationInfo()
info.shouldSendContentAvailable = true
info.alertBody = "" //This needs to be set or pushes don't always get sent
subscription.notificationInfo = info
= 6 =
For an iOS app, my app delegate handles notifications like this:
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
//Ask Permission for Notifications
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: { authorized, error in
DispatchQueue.main.async {
if authorized {
UIApplication.shared.registerForRemoteNotifications()
}
}
})
return true
}
//MARK: Background & Push Notifications
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]{
let dict = userInfo as! [String: NSObject]
let notification = CKNotification(fromRemoteNotificationDictionary: dict)
if let sub = notification.subscriptionID{
print("iOS Notification: \(sub)")
}
}
//After we get permission, register the user push notifications
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
//Add your CloudKit subscriptions here...
}
}
Getting permission for notifications isn't required if you are only doing background pushes, but for anything the user sees in the form of a popup notification, you must get permission. If your app isn't asking for that permission, try deleting it off your device and building again in Xcode.
Good luck! : )
I am using RxCloudKit library, here's an a code snippet of how it handles query notifications -
public func applicationDidReceiveRemoteNotification(userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
let dict = userInfo as! [String: NSObject]
let notification = CKNotification(fromRemoteNotificationDictionary: dict)
switch notification.notificationType {
case CKNotificationType.query:
let queryNotification = notification as! CKQueryNotification
self.delegate.query(notification: queryNotification, fetchCompletionHandler: completionHandler)
...
This method is called from func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
Before you can receive notifications, you will need to do the following -
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
application.registerForRemoteNotifications()
...
UPDATE:
Info.plist should contain the following -
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
Update: As Reinhard mentioned in his comment: you can in fact still subscribe to changes from the public database and manually import the changes into Core Data. Still I am unsure whether it is a good idea to rely on subscriptions in the public database if Apple specifically mentioned these differences
Original answer:
I don't think the accepted answer fully answers the question here.
Short answer would be that the CloudKit public database does not support subscriptions like the private database. Instead only a polling mechanism can be used. NSPersistentCloudKitContainer handles this automatically, but only updates very rarely.
This talk from WWDC2020 explains this in detail and I recommend watching it because there are other important details mentioned where public database differs from private database: https://developer.apple.com/wwdc20/10650
In the talk they mentioned thet a pull is initiated on each app start and after about 30 mins of application usage.

AppDelegate Never Gets Its didReceiveRemoteNotification Called For CKQuerySubscription

I'm trying to let the iOS app listen to CKQuerySubscription changes. Data is transmitted by a remote iOS app. I already have a macOS application, which does receive data sent by the remote iOS app. The iOS app I have trouble with already has a subscription. Yet, its AppDelegate never receives a call in the didReceiveRemoteNotification method.
import UIKit
import UserNotifications
import CloudKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
/* notifications */
let center = UNUserNotificationCenter.current()
center.delegate = self
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
switch settings.authorizationStatus {
case .authorized:
print("You already have permission")
DispatchQueue.main.async() {
application.registerForRemoteNotifications()
}
case .denied:
print("setting has been disabled")
case .notDetermined:
print("Let me ask")
UNUserNotificationCenter.current().requestAuthorization(options: []) { (granted, error) in
if error == nil {
if granted {
print("you are granted permission")
DispatchQueue.main.async() {
application.registerForRemoteNotifications()
}
}
}
}
}
}
return true
}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register notifications_ error:", error)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Receiving data...") // never called...
}
}
I have some capabilities on as shown below. I don't know if the app needs push notifications. For now, it's turned on.
So why doesn't my iOS app get the remote notification call? I'm using the app with an actual device, not a simulator. Thanks.
EDIT: Creating a subscription to a record change
class HomeViewController: UIViewController {
override func viewDidLoad() {
registerSubscription()
}
func registerSubscription() {
let cloudContainer = CKContainer(identifier: "iCloud.com.xxx.XXXXX")
let privateDB = cloudContainer.privateCloudDatabase
let predicate = NSPredicate(format: "TRUEPREDICATE")
let subscription = CKQuerySubscription(recordType: "PrivateRecords", predicate: predicate, options: .firesOnRecordCreation)
let notification = CKNotificationInfo()
subscription.notificationInfo = notification
privateDB.save(subscription, completionHandler: ({returnRecord, error in
if let err = error {
print("Subscription has failed: \(err.localizedDescription)")
} else {
print("Subscription set up successfully")
print("Subscription ID: \(subscription.subscriptionID)")
}
}))
}
}
There are a few more things you can check.
First, make sure you implement didReceiveRemoteNotification in your app delegate:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
let dict = userInfo as! [String: NSObject]
let notification = CKNotification(fromRemoteNotificationDictionary: dict)
if let sub = notification.subscriptionID{
print("iOS Notification Received: \(sub)")
}
}
There are also a few other things you can check:
Try deleting your CKQuerySubscription in the CloudKit dashboard, then run your iOS code again that registers it. Does the subscription show up in the dashboard?
Does the CloudKit log show that a notification was sent? It lists all notifications that were pushed to a device.
If you are using silent push notifications, try enabling Background fetch in the Background Modes capability (right above Remote notifications).
If you do all that and it still doesn't work, can you share your CKQuerySubscription code?
-- Update --
Try setting some additional attributes on your CKNotificationInfo object. There are some obscure bugs with notifications that can usually be circumvented by setting a couple properties like this:
notification.shouldSendContentAvailable = true
notification.alertBody = "" //(Yes, a blank value. It affects the priority of the notification delivery)
You can also try setting your predicate to: NSPredicate(value: true)
Also, what does your privateDB.save method return? Does it say it succeeds or fails?

Digits to firebase migration iOS Phone number input

I have been using digits for login via phone number. I have to migrate to firebase but there are a few things I am confused about:
My flow was:
1) User clicked on a custom button login via phone number which had action
#IBAction func loginViaDigits(_ sender: AnyObject) {
let digits = Digits.sharedInstance()
let configuration = DGTAuthenticationConfiguration(accountFields: .defaultOptionMask)
configuration?.phoneNumber = "+44"
digits.authenticate(with: self.navigationController, configuration: configuration!) { session, error in
if error == nil {
let digits = Digits.sharedInstance()
let oauthSigning = DGTOAuthSigning(authConfig:digits.authConfig, authSession:digits.session())
let authHeaders = oauthSigning?.oAuthEchoHeadersToVerifyCredentials()
self.startActivityIndicator()
NetworkApiCall(apiRequest: SignInApiRequest(digits_auth: authHeaders as! [NSObject : AnyObject])).run() { (result: SignInApiResponse?, error: NSError?) in
if error != nil {
self.stopActivityIndicator()
UIUtils.showInfoAlert("Some error occurred!", controller: self)
return;
}
guard let response = result else {
self.stopActivityIndicator()
UIUtils.showInfoAlert("Some error occurred!", controller: self)
return;
}
...
}
}
}
2) Basically, user clicked on login via phone number button and digits showed their popup for getting phone number and then they asked for verification code and when they were done, I would get the oauth params in my callback method and I passed them on to my server.
My question is:
1) Do I need to build both phone number input and verification code input screens myself or firebase is providing them like digits did?
2) If someone has actually migrated this kind of flow already, some pointers would be very helpful.
As suggested by Lazy King, I am trying to use FirebaseAuthUI, but my AppDelegate seems to be missing some function:
My AppDelegate changes for Firebase:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Pass device token to auth
Auth.auth().setAPNSToken(deviceToken, type: AuthAPNSTokenType.prod)
}
func application(_ application: UIApplication,
didReceiveRemoteNotification notification: [AnyHashable : Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if Auth.auth().canHandleNotification(notification) {
completionHandler(.noData)
return
}
// This notification is not auth related, developer should handle it.
}
But I still keep getting this error:
Error Domain=FIRAuthErrorDomain Code=17054 "If app delegate swizzling is disabled, remote notifications received by UIApplicationDelegate need to be forwarded to FIRAuth's canHandleNotificaton: method."
You can use FirebaseAuth UI or design a UI for your own. For one of my project I used FirebaseAuth UI. Here is step by step:
Add FireBase Auth and Auth UI
pod 'Firebase/Auth', '~> 4.0.0' and
pod 'FirebaseUI/Phone', '~> 4.0'
In Appdelegate file register for push notification, this is mandatory. Google user push notification for first verification.
Add this line on didRegisterForRemoteNotificationsWithDeviceToken:
Auth.auth().setAPNSToken(deviceToken, type:AuthAPNSTokenType.prod)//.sandbox for development
You also need set up Google notification on you Firebase console
on Phone log in button function
var uiAuth = FUIAuth.defaultAuthUI() ;
uiAuth?.delegate = self;
var phoneVC = FUIPhoneAuth(authUI: uiAuth!)
uiAuth.providers = [phoneVC];
phoneVC.signIn(withPresenting: self)
implement delegate function
in Appdelegate receive notification function add code
if Auth.auth().canHandleNotification(userInfo) {
completionHandler(UIBackgroundFetchResult.noData)
return
}
If you use iOS 10 or later then
#available(iOS 10.0, *)
internal func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if Auth.auth().canHandleNotification(userInfo) {
completionHandler()
return
}
completionHandler()
}
Hope it will work.
please register notification before Firebase.configure().
it works

Receiving Push Notifications on Swift with AWS Mobile Hub

I am currently developing and iOS app using Swift, which I am new to, and the code generated from AWS Mobile Hub, with AWS SNS to register devices and send notifications.
On my class AWSMobileClient I have the following code:
func didFinishLaunching(_ application: UIApplication, withOptions launchOptions: [AnyHashable: Any]?) -> Bool {
print("didFinishLaunching:")
// Register the sign in provider instances with their unique identifier
AWSSignInProviderFactory.sharedInstance().register(signInProvider: AWSFacebookSignInProvider.sharedInstance(), forKey: AWSFacebookSignInProviderKey)
var didFinishLaunching: Bool = AWSIdentityManager.default().interceptApplication(application, didFinishLaunchingWithOptions: launchOptions)
didFinishLaunching = didFinishLaunching && AWSPushManager(forKey: ServiceKey).interceptApplication(application, didFinishLaunchingWithOptions: launchOptions)
if (!isInitialized) {
AWSIdentityManager.default().resumeSession(completionHandler: { (result: Any?, error: Error?) in
print("Result: \(result) \n Error:\(error)")
}) // If you get an EXC_BAD_ACCESS here in iOS Simulator, then do Simulator -> "Reset Content and Settings..."
// This will clear bad auth tokens stored by other apps with the same bundle ID.
isInitialized = true
}
return didFinishLaunching
}
Which is called normally.
On my AppDelegate, I have the following:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
AWSMobileClient.sharedInstance.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
NotificationCenter.default.post(name: Notification.Name(rawValue: AWSMobileClient.remoteNotificationKey), object: deviceToken)
print("###--- DID REGISTER FOR REMOTE NOTIFICATION ---###")
}
Which is also called.
However, when I try sending a notification using AWS SNS, my function:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("###--- DID RECEIVE REMOTE NOTIFICATION ---###")
AWSMobileClient.sharedInstance.application(application, didReceiveRemoteNotification: userInfo , fetchCompletionHandler: completionHandler)
// This is where you intercept push notifications.
if (application.applicationState == .active) {
UIAlertView.init(title: "Notification Received", message: userInfo.description, delegate: nil, cancelButtonTitle: "OK").show()
}
}
Is never called.
Looking for a solution I read that since iOS 10 there are some chances that need to be made to deal with push notification, but I'm not sure about the correct ways.
How should I implement the code to receive the notifications?

Which is the best way to test a background URLSession?

I'm following a sample code to download several images and show them in cells. For that, I configured an URLSession like this:
let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "com.myexample.images")
self.backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)
Then, I perform the download of an image like this:
func downloadImage(imageUrl url: URL, imageId: Int, completion: ImageResult?) -> URLSessionDownloadTask? {
let request = URLRequest(url: url)
let task = backgroundSession.downloadTask(with: request)
// Code here to keep track of the completion handler for this task
task.resume()
return task
}
I also conform to URLSessionDownloadDelegate and implement its didCompleteWithError, didFinishDownloadingTo and urlSessionDidFinishEvents methods. For this last method, I have this implementation:
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate, let completionHandler = appDelegate.backgroundSessionCompletionHandler {
appDelegate.backgroundSessionCompletionHandler = nil
completionHandler()
}
}
And then in AppDelegate:
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
I don't know if this is actually working. I run the app in a simulator, I go to phone's Home to put the app in background state and I'm not able to see if neither urlSessionDidFinishEvents nor app delegate's handleEventsForBackgroundURLSession methods are called. Maybe because the downloads are too fast to see this.
How could I properly test this in a simulator? Am I missing something?
call this method downloadImage on BackgroundFetch of Appdelegate
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Swift.Void)
and then in debug mode, run app on device, then lock your phone and then from XCode - Debug Menu, select Simulate Background Fetch

Resources