Notification does not trigger userNotificationCenter on macOS Big Sur 11.6 - swift

When I run app using terminate argument (set using “Product“ / “Scheme” / “Edit Scheme…” / “Run” / “Arguments” / “Argument Passes On Launch”), a notification appears in macOS Notification Centre and app terminates.
#testapp application did finish launching
#testapp terminate mode enabled
#testapp terminating…
So far, so good… Expected.
When I click notification, app launches but userNotificationCenter is not triggered (I don’t see #testapp notification triggered in Console app, but I see following).
#testapp application did finish launching
#testapp terminating…
Not normal right? How can I fix this?
I am starting to think that this is a Big Sur bug in version 11.6.
Everything works fine on Big Sur version 11.4 (M1) and 11.5 (Intel).
Thanks for helping out!
//
// AppDelegate.swift
// Test
//
// Created by Sun Knudsen on 2021-10-22.
//
import Cocoa
import UserNotifications
#main
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate {
func showNotification(){
let content = UNMutableNotificationContent()
content.body = "Hello"
let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: nil
)
UNUserNotificationCenter.current().add(request)
}
func terminate() -> Void {
DispatchQueue.main.async {
NSApp.terminate(self)
}
}
func applicationDidFinishLaunching(_ notification: Notification) {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (allowed, error) in
NSLog("#testapp application did finish launching")
if CommandLine.arguments.indices.contains(1) && CommandLine.arguments[1] == "terminate" {
NSLog("#testapp terminate mode enabled")
self.showNotification()
self.terminate()
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.terminate()
}
}
}
}
func applicationWillTerminate(_ aNotification: Notification) {
NSLog("#testapp terminating…")
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void
) {
NSLog("#testapp notification triggered")
self.terminate()
completionHandler()
}
}
Test app available on GitHub at https://github.com/sunknudsen/test-app.

This issue is caused by a bug in macOS Big Sur 11.6.
Everything works as expected in macOS Big Sur 11.6.1 or macOS Monterey.

Related

watchOS, not receiving remote notifications

I am developing app on WatchOS 6 but I cannot receive remote notification
Here is my registration code for push notifications in ExtensionDelegate I am getting valid device token.
extension ExtensionDelegate {
func setupRemoteNotifications() {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
print("[WATCH PUSH NOTIFICATIONS] Permission granted: \(granted)")
guard granted else {
DispatchQueue.main.async {
self.showNotificationsNotGrantedAlert()
return
}
return
}
self.getNotificationSettings()
}
}
private func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("[WATCH PUSH NOTIFICATIONS] Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
WKExtension.shared().registerForRemoteNotifications()
self.onRemoteNotificationRegistration()
}
}
}
private func onRemoteNotificationRegistration() { }
func didRegisterForRemoteNotifications(withDeviceToken deviceToken: Data) {
// Convert token to string
let deviceTokenString = deviceToken.map { data in String(format: "%02.2hhx", data) }.joined()
print("[WATCH PUSH NOTIFICACTIONS] Device Token: \(deviceTokenString)")
UserSettings.shared.deviceToken = deviceTokenString
}
func didFailToRegisterForRemoteNotificationsWithError(_ error: Error) {
print("[WATCH PUSH NOTIFICATIONS] Failed to register device: \(error)")
UserSettings.shared.deviceToken = nil
}
func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (WKBackgroundFetchResult) -> Void) {
print("[WATCH PUSH NOTIFICATIONS] Push notification received: \(userInfo)")
let aps = userInfo["aps"] as! [String: AnyObject]
completionHandler(.noData)
}
}
extension ExtensionDelegate: UNUserNotificationCenterDelegate {
// show notification also when in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("[WATCH PUSH NOTIFICATION] Will present notification...")
let categoryIdentifier = notification.request.content.categoryIdentifier
let category = NotificationCategory(rawValue: categoryIdentifier)
if category == NotificationCategory.NotificationCategory {
} else if category == NotificationCategory.ActivityCategory {
}
completionHandler([.alert, .badge, .sound])
}
// called when tapped onto notification banner
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("[WATCH PUSH NOTIFICATION] Did receive notification response")
let userInfo = response.notification.request.content.userInfo as! [String: AnyObject]
let aps = userInfo["aps"] as! [String: AnyObject]
let categoryIdentifier = response.notification.request.content.categoryIdentifier
let category = NotificationCategory(rawValue: categoryIdentifier)
if category == NotificationCategory.NotificationCategory {
} else if category == NotificationCategory.ActivityCategory {
}
handleNotificationAction(response.actionIdentifier)
openNotification(userInfo: userInfo)
completionHandler()
}
}
extension ExtensionDelegate {
private func handleNotificationAction(_ actionIdentifier: String) {
let action = NotificationAction(rawValue: actionIdentifier)
if action == NotificationAction.Call {
print("Action: Call handled!")
} else if action == NotificationAction.Email {
print("Action: Email handled!")
} else if action == NotificationAction.Message {
print("Action: Message handled!")
}
}
private func openNotification(userInfo: [String: AnyObject]) {
// let something = userInfo["something"] ...
}
}
extension ExtensionDelegate {
private func showNotificationsNotGrantedAlert() {
let settingsActionTitle = NSLocalizedString("Settings", comment: "")
let cancelActionTitle = NSLocalizedString("Cancel", comment: "")
let message = NSLocalizedString("You need to grant a permission from notification settings.", comment: "")
let title = NSLocalizedString("Push Notifications Off", comment: "")
let settingsAction = WKAlertAction(title: settingsActionTitle, style: .default) {
print("[WATCH PUSH NOTIFICATIONS] Go to Notification Settings")
}
let cancelAction = WKAlertAction(title: cancelActionTitle, style: .cancel) {
print("[WATCH PUSH NOTIFICATIONS] Cancel to go to Notification Settings")
}
WKExtension.shared().rootInterfaceController?.presentAlert(withTitle: title, message: message, preferredStyle: .alert, actions: [settingsAction, cancelAction])
}
}
I've added Push notification Entitlement in WatchExtensions
I am sending notification from Push Notification Tester app using valid TeamId, P8 cert, P8 key, bundle Id -> where I am getting success.
I am sending default test notification nothing special
{
"aps": {
"alert": {
"title": "Silver Salmon Creek",
"body": "You are within 5 miles of Silver Salmon Creek."
},
"category": "Notification"
}
}
I have similar code for iOS and there notification are delivered correctly on iPhone.
UPDATE
I've tested a little bit more
And I have application added to my iPhone app
but iPhone app is for iOS 13
and Watch app is for WatchOS 6
So as I understand this WatchOS app can be install as standalone application.
I have checked "Support running without iOS App installation"
So as I understand my server should send this notification to both
Watch device Token and iOS device token. And there APNS/iOS/watchOS take the decision if there is duplicated notification where to deliver this notification i.e. to iPhone / watchOS.
If there is only watchOS app I think it will be delivered to watch Device token
If there is both watch/iOS app then it will send to iOS and then device whether user is using iPhone/Watch in given moment and deliver it to currently used device.
So now if I send notification to iPhone device token with iOS bundle identifier, and have locked iPhone and watch on wrist I am getting notification on my WatchOS and even can look at long notifications with dynamic content.
But when I've uninstall iOS version of application, and send notification with iPhone or watch device token and appropriate bundle id iOS/watchOS then for standalone app I am not getting this notification delivered again.
I encountered a similar issue, be sure your p8 Bundle ID is using the explicit WatchkitApp ID and not the Extension's ID.
Once I dropped the .watchkitextension and uninstalled (the app), reinstalled, reregistered the device, it worked.
I found the issue. It was related with the way Push Notification is send from the server using node-apns. I also have old Push Notification Tester app. 3.0 version of node-apns is required. there is additonal Header field send to APNS server
apns-push-type

Unable to show simple notification on macOS

Im Sort of new to Swift. I trying to create a console app/utility that creates a notification pop-up on macOS. I cant seem to find any way of doing this without. Its seems like this should be simple but im unable to get the notification pop-up to show :(
Im using swift 4.0.3 on macOS Sierra
Any guidance to get this work at the most basic level would be greatly appreciated! thanks!
import Foundation
import Cocoa
let notification = NSUserNotification()
notification.title = "Title of notification"
notification.subtitle = "Subtitle of notification"
notification.soundName = NSUserNotificationDefaultSoundName
NSUserNotificationCenter.default.deliver(notification)
Your notification is only delivered when your app is not focused.
If you want to deliver it anyway, you need to implement NSUserNotificationCenterDelegate as a protocol and these functions:
func userNotificationCenter(_ center: NSUserNotificationCenter, shouldPresent notification: NSUserNotification) -> Bool { return true }
private func userNotificationCenter(_ center: NSUserNotificationCenter, didActivate notification: NSUserNotification) -> Bool { return true }
public func userNotificationCenter(center: NSUserNotificationCenter, shouldPresentNotification notification: NSUserNotification) -> Bool { return true }
PS. I'm pretty sure, that not all of them are needed - but I don't know which ones are

Swift 3 / Firebase 4: Notifications from Firebase show alerts, but notifications from my server only show up in console

In a Swift 3 app, implementing Firebase 4 for notifications and a WKWebView for displaying an Angular web app I've come to a point where I can receive app notifications from Firebase and get an alert in the Notification Centre.
But when attempting to get app notifications from my server I only end up having them displayed in the console when running the app, but the phone does not display notifications at all.
I'm suspecting an issue with the way the messages from my server are formatted, below is the console output from a successful alert from Firebase:
[AnyHashable("google.c.a.e"): 1, AnyHashable("google.c.a.ts"): 1504280715, AnyHashable("google.c.a.udt"): 0, AnyHashable("gcm.n.e"): 1, AnyHashable("aps"): {
alert = "This one works";
}, AnyHashable("google.c.a.c_id"): 7239776096663233136,
AnyHashable("gcm.message_id"): 0:1504280715823529%dc6002bbdc6002bb]
And here is a sample from my own app server, which does not display as a phone notification:
[AnyHashable("from"): 1001513747966, AnyHashable("body"): Urgent action
is needed to prevent your account from being disabled!,
AnyHashable("title"): Urgent action needed!]
Message ID: 0:1504280715823529%dc6002bbdc6002bb
I'm testing this on ios10 devices, only.
My AppDelegate.swift file looks like this:
import UIKit
import CoreData
import Fabric
import Crashlytics
import Firebase
import FirebaseMessaging
import UserNotifications
import FirebaseInstanceID
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let gcmMessageIDKey = "gcm.message_id"
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
// [START set_messaging_delegate]
Messaging.messaging().delegate = self as MessagingDelegate
// [END set_messaging_delegate]
// Register for remote notifications. This shows a permission dialog on first run, to
// show the dialog at a more appropriate time move this registration accordingly.
// [START register_for_notifications]
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)
}
Messaging.messaging().shouldEstablishDirectChannel = true
application.registerForRemoteNotifications()
// [END register_for_notifications]
Fabric.with([Crashlytics.self])
return true
}
// [START receive_message]
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}
// [END receive_message]
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Unable to register for remote notifications: \(error.localizedDescription)")
}
// This function is added here only for debugging purposes, and can be removed if swizzling is enabled.
// If swizzling is disabled then this function must be implemented so that the APNs token can be paired to
// the InstanceID token.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("APNs token retrieved: \(deviceToken)")
// With swizzling disabled you must set the APNs token here.
Messaging.messaging()
.setAPNSToken(deviceToken, type: MessagingAPNSTokenType.unknown)
}
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().shouldEstablishDirectChannel = true
}
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.
//connectToFirebaseMessaging()
UIApplication.shared.applicationIconBadgeNumber = 0
}
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: "ViDent")
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)")
}
}
}
}
// [START ios_10_message_handling]
#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
// With swizzling disabled you must let Messaging know about the message, for Analytics
Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
// Change this to your preferred presentation option
completionHandler([])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
completionHandler()
}
}
// [END ios_10_message_handling]
extension AppDelegate : MessagingDelegate {
// [START refresh_token]
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
}
// [END refresh_token]
// [START ios_10_data_message]
// Receive data messages on iOS 10+ directly from FCM (bypassing APNs) when the app is in the foreground.
// To enable direct data messages, you can set Messaging.messaging().shouldEstablishDirectChannel to true.
func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
print("Received data message: \(remoteMessage.appData)")
}
// [END ios_10_data_message]
And it is the print("Received data message: (remoteMessage.appData) at the very end of the AppDelegate.swift file which triggers and prints the message to the console, so I just need to work out how to pass these messages on so an alert is triggered on the device.

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.

Very slow communication between apple watch and iPhone

I have wrote a very basic Xcode project that contains 3 targets:
- iOS target
- WatchKit app
- WatchKit extension
First of all, i do not understand why Xcode creates a second target (extension) for WatchKit app ? It seems that WatchKit app contains storyboard, and WatchKit extension contains swift code (controllers). Is there a particular reason for Xcode to design and split 2 targets instead of one single ?
Look at this very basic piece of code:
iOS controller:
override func viewDidLoad()
{
super.viewDidLoad()
if WCSession.isSupported()
{
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
#IBAction func on_btn_tap(_ sender: Any)
{
if WCSession.isSupported()
{
let session = WCSession.default()
session.sendMessage(["mykey": "myvalue"], replyHandler: { (response) -> Void in
NSLog("OK")
}, errorHandler: { (error) -> Void in
NSLog("Error)
})
}
}
On watch extension (InterfaceController.swift):
override func awake(withContext context: Any?)
{
super.awake(withContext: context)
if WCSession.isSupported()
{
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
extension InterfaceController: WCSessionDelegate
{
func session(_ session: WCSession,
activationDidCompleteWith activationState: WCSessionActivationState,
error: Error?)
{
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void)
{
self.btn.setBackgroundColor(UIColor.yellow)
}
}
As you certainly understand, i have a button on my iOS App. When i tap on this button, i send a message to Watch App and this app will change a button color.
There is a delay of about 5-6 seconds between the button tap on the iPhone and the color change. Do you know why ?
In the other communication side (watch to iPhone), it is worst (10-15 seconds)
Thanks
Since you are updating your UI you need to wrap it in a DispatchQueue, like this:
DispatchQueue.main.async {
self.btn.setBackgroundColor(UIColor.yellow)
}
These delegate callbacks are not on the main thread and you should never update your UI from any other thread than the main thread. Wrapping it like this results in much faster updating of your UI and safer code.