CloudKit subscriptions in tvOS - swift

I am trying to sync data between a few Apple TVs using CloudKit and CKSubcription. The problem is application:didReceiveRemoteNotification: is never called when I add, delete, or update records. I believe I am configuring the subscriptions correctly and I have confirmed they are added to the CloudKit dashboard. I have repeatedly tried resetting the development environment in the dashboard, but that is not helping. I really don't want to create a timer to fetch every so often. Thanks for any help!
Also, I am using the private database in CloudKit and not the public database if that matters.
Here's my code:
AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
CloudKitManager.subscribeToItemUpdates()
application.registerForRemoteNotifications()
...
return true
}
CloudKitManager.swift
class func subscribeToItemUpdates() {
if let uuid = UIDevice.currentDevice().identifierForVendor?.UUIDString {
saveSubscriptionWithIdentifier(uuid + "create", options: CKSubscriptionOptions.FiresOnRecordCreation)
saveSubscriptionWithIdentifier(uuid + "update", options: CKSubscriptionOptions.FiresOnRecordUpdate)
saveSubscriptionWithIdentifier(uuid + "delete", options: CKSubscriptionOptions.FiresOnRecordDeletion)
}
}
class func saveSubscriptionWithIdentifier(identifier: String, options: CKSubscriptionOptions) {
let sub = CKSubscription(recordType: "Message", predicate: NSPredicate(value: true), subscriptionID: identifier, options: options)
sub.notificationInfo = CKNotificationInfo()
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
publicDatabase.saveSubscription(sub) { (savedSubscription, error) -> Void in
if error != nil {
print("Error saving CloudKit subscription \(error)")
}
else {
print("Saved subscription to CloudKit", savedSubscription)
}
}
}

There is a slight difference between tvOS and iOS. In my demo app i handle it like this:
#if os(tvOS)
//This will only be called when your app is active. So this is what you should use on tvOS
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
EVLog("Push received")
EVCloudData.publicDB.didReceiveRemoteNotification(userInfo, executeIfNonQuery: {
EVLog("Not a CloudKit Query notification.")
}, completed: {
EVLog("All notifications are processed")
})
}
#else
// Process al notifications even if we are in the background. tvOS will not have this event
// Make sure you enable background notifications in the app settings. (entitlements: pushnotifications and backgrounds modes - notifications plus background fetch)
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
EVLog("Push received")
EVCloudData.publicDB.didReceiveRemoteNotification(userInfo, executeIfNonQuery: {
EVLog("Not a CloudKit Query notification.")
}, completed: {
EVLog("All notifications are processed")
completionHandler(.NewData)
})
}
#endif
For more information see EVCloudKitDao

Related

SwiftUI swizzling disabled by default, phone auth not working

I am building a screen with phone number login. I checked over and over again and the project is newly created, however, I am getting this log:
7.2.0 - [Firebase/Auth][I-AUT000015] The UIApplicationDelegate must handle remote notification for phone number authentication to work.
If app delegate swizzling is disabled, remote notifications received by UIApplicationDelegate need to be forwarded to FIRAuth's canHandleNotificaton: method.
I did read in the documentation about swizzling and I don't know why it seems to be disabled, I did not disabled it. I have added GoogleServices-Info.plist into the app, I added in firebase panel the app apn auth key.
My entry point in the app looks like this:
#main
struct partidulverdeApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
MainView()
.onOpenURL { url in
Auth.auth().canHandle(url.absoluteURL)
}
}
}
}
My URL Types property has an entry with the RESERVED_CLIENT_ID
I am very desperate about this problem. Any idea is highly appreciated.
Edit1:
I did read the documentation and tried to handle notification with swizzling disabled, but I get the same error:
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didReceiveRemoteNotification notification: [AnyHashable : Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if Auth.auth().canHandleNotification(notification) {
completionHandler(.noData)
return
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("Your code here")
return true
}
}
#main
struct partidulverdeApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
MainView()
.onOpenURL { url in
Auth.auth().canHandle(url.absoluteURL)
}
}
}
}
Here's how to implement Phone Number Auth using the new SwiftUI 2 life cycle:
Create a Firebase project and set up PhoneNumber Auth
Add your iOS app to the Firebase project, download and add GoogleService-Info.plist to your project
In Xcode, select the application target and enable the following capabilities:
Push notifications
Background modes > Remote notifications
Create and register an APNS authentication key on the Apple developer portal
Upload the key to Firebase (under Project Settings > Cloud messaging in the Firebase Console)
Add the Firebase project's reversed client ID to your app's URL schemes
In your Info.plist, set FirebaseAppDelegateProxyEnabled to NO
Implement the AppDelegate as follows:
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
print("SwiftUI_2_Lifecycle_PhoneNumber_AuthApp application is starting up. ApplicationDelegate didFinishLaunchingWithOptions.")
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("\(#function)")
Auth.auth().setAPNSToken(deviceToken, type: .sandbox)
}
func application(_ application: UIApplication, didReceiveRemoteNotification notification: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("\(#function)")
if Auth.auth().canHandleNotification(notification) {
completionHandler(.noData)
return
}
}
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
print("\(#function)")
if Auth.auth().canHandle(url) {
return true
}
return false
}
}
#main
struct SwiftUI_2_Lifecycle_PhoneNumber_AuthApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
print("Received URL: \(url)")
Auth.auth().canHandle(url) // <- just for information purposes
}
}
}
}
For further reading, I suggest these two articles I wrote:
Firebase and the new SwiftUI 2 Application Life Cycle
The Ultimate Guide to the SwiftUI 2 Application Life Cycle

Push notification with Engage Digital (formerly Dimelo) didReceiveRemoteNotification never called

I send notification I receive it on the phone no problem;
Now I want to customise image titre ...
the problem is this function on delegate was never called didReceiveRemoteNotification
On appDelegate didFinishLaunchingWithOptions:
// Dimelo: Push Notif and Badge
dimelo?.updateAppBadgeNumber = true
dimelo?.developmentAPNS = true
dimelo?.initialize(withApiSecret: BuildConfig.GetInstance().getDimeloApiSecret(), domainName: BuildConfig.GetInstance().getDimeloDomainName(), delegate: self)
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Register the device token.
Dimelo.sharedInstance().deviceToken = deviceToken
}
func dimeloDidBeginNetworkActivity(_ dimelo: Dimelo?) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func dimeloDidEndNetworkActivity(_ dimelo: Dimelo?) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
dimelo?.consumeReceivedRemoteNotification(userInfo)
}
I already activate remote notification on baclground mode
but the function didReceiveRemoteNotification was never called :(
I'm integrating this RingCentral Engage Digital / Dimelo library:
https://github.com/ringcentral/engage-digital-messaging-ios/issues
I have implemented this demo app using the Dimelo iOS SDK: https://github.com/tylerlong/GrandTravel-iOS/blob/master/GrandTravel/AppDelegate.swift
Could you please check my code and figure out the differences?
I suggest you to print logs for both handleActionWithIdentifier and didReceiveRemoteNotification.
If it still doesn't work, please send email to devsupport#ringcentral.com and we will investigate.

Receiving Firebase Notifications in Foreground but not Background

I've tried everything to get the alerts to pop up while in the background. I receive the data when app is open or while launching. Because I receive the notifications I'm assuming it's in my AppDelegate code or perhaps something wrong in my .plist??? I've followed a few of the standard tutorials on firebase notifications, all this code is from those tutorials.
import UIKit
import CoreData
import Firebase
import UserNotifications
import FirebaseInstanceID
import FirebaseMessaging
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> 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 })
// For iOS 10 data message (sent via FCM
Messaging.messaging().delegate = self
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
// Override point for customization after application launch.
NotificationCenter.default.addObserver(self, selector: #selector(self.tokenRefreshNotification(_:)),
name: NSNotification.Name.InstanceIDTokenRefresh, object: nil)
return true
}
func application(application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("refreshed token")
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
// Messaging.messaging().disconnect()
// print("Disconnected from FCM.")
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
connectToFcm()
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "firetail")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
#objc private func tokenRefreshNotification(_ notification: Notification) {
if let refreshedToken = InstanceID.instanceID().token() {
print("InstanceID token: \(refreshedToken)")
}
// Connect to FCM since connection may have failed when attempted before having a token.
connectToFcm()
}
func connectToFcm() {
// Won't connect since there is no token
guard InstanceID.instanceID().token() != nil else {
return
}
// Disconnect previous FCM connection if it exists.
Messaging.messaging().disconnect()
Messaging.messaging().connect { (error) in
if error != nil {
print("Unable to connect with FCM. \(error?.localizedDescription ?? "")")
} else {
print("Connected to FCM.")
}
}
}
public func application(received remoteMessage: MessagingRemoteMessage) {
print(remoteMessage.appData)
}
}
A few things to note:
The simulator cannot receive push notifications.
You must have push notifications and background modes (remote notifications & background fetch) enabled in your project's capabilities.
Try adding these lines of code to your app delegate:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// Will not be called until you open your application from the remote notification (returns to foreground)
// Note: *with swizzling disabled you must let Messaging know about the message
// Messaging.messaging().appDidReceiveMessage(userInfo)`
// Print message ID.
if let messageId = userInfo["gcm.message_id"] {
print("Message Id: \(messageId)")
}
// Print full message.
print(userInfo)
Messaging.messaging().appDidReceiveMessage(userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// Will not be called until you open your application from the remote notification (returns to foreground)
// Note: *with swizzling disabled you must let Messaging know about the message
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message id
if let messageId = userInfo["gcm.message_id"] {
print("Message Id: \(messageId)")
}
// Print full message.
print(userInfo)
Messaging.messaging().appDidReceiveMessage(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
I also noticed you map your APNS token to Messaging (in didRegisterForRemoteNotificationsWithDeviceToken) using :
Messaging.messaging().apnsToken = deviceToken
I would try your luck replacing it with the following:
Messaging.messaging().setAPNSToken(deviceToken, type: MessagingAPNSTokenType.sandbox)
Messaging.messaging().setAPNSToken(deviceToken, type: MessagingAPNSTokenType.prod)
Good luck!
Be sure to send your messages through Firebase with the high priority setting instead of the standard/default priority.

watchOS2 app and iPhone app communication

In the watchOS1, we had a method “openParentApplication”. This method communicated with the phone application even when it wasn’t running in foreground or background and fetched a reply immediately. I need something similar for watchOS2. I want my watch application to communicate immediately with the phone app even if my iPhone application is not running. Methods like updateApplicationContext:error:, sendMessage:replyHandler:errorHandler: and transferUserInfo: are not helpful in this scenario.
Please can someone suggest me a better approach to achieve this?
Actually sendMessage:replyHandler:errorHandler: is doing exactly what you are asking for. As long as your watch is connected to your phone it immediately gets a response to the message. This is working when the app is in the foreground, in the background or not running at all.
Here is how you set it up:
In the WatchExtension:
Setup the session. Typically in your ExtensionDelegate:
func applicationDidFinishLaunching() {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
And then send the message when you need something from the app:
if WCSession.defaultSession().reachable {
let messageDict = ["message": "hello iPhone!"]
WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
print(replyDict)
}, errorHandler: { (error) -> Void in
print(error)
}
}
In the iPhone App:
Same session setup, but this time also set the delegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
...
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
And then implement the delegate method to send the reply to the watch:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
replyHandler(["message": "Hello Watch!"])
}
This works whenever there is a connection between the Watch and the iPhone. If the app is not running, the system starts it in the background. So, basically it just works like openParentApplication(_:reply:)

Get notification when app is in background mode

I want to call a function when I receive a notification, even when my application is in background
I found a lot of question / answer on stackoverflow
but in me it does not. So I will explain everything I 've done
i have background mode activate
here
my info.plist is good
here
in my didFinishLaunchingWithOption i have
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: .Badge | .Sound | .Alert, categories: nil))
application.registerForRemoteNotifications()
and i have
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
println("NOTIFICATION 4");
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
println("NOTIFICATION FETCH");
}
but i can't see my log when i send my notification
my notification is send like this
{ deviceToken: XXXXX,
expiration: 1442225011,
identifier: 0,
payload:
{ 'content-available': '1',
aps: { badge: 0, alert: 'Hello' } } }
I do not know what to do more ... I really can not receive this notification, nothing happens ( I receive the banner but no call to my function)
thank's a lot
As you can read in documents:
The aps dictionary can also contain the content-available property. The content-available property with a value of 1 lets the remote notification act as a “silent” notification.
so you should insert content-available in aps dictionary, like this:
{ deviceToken: XXXXX,
expiration: 1442225011,
identifier: 0,
payload:
{ aps: { 'content-available': '1', badge: 0, alert: 'Hello' } } }