watchOS, not receiving remote notifications - swift

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

Related

Badge variable is missing in my FCM notification in Swift

I am receiving notifications in my app using FCM but i am unable to show badge count on my app icon since there seems to be missing variables in my notification. how can i add this?
Below is the error i am getting
[AnyHashable("icon"): logo.png, AnyHashable("user_id"): 3, AnyHashable("google.c.sender.id"): 938224596608, AnyHashable("aps"): {
alert = {
body = "You have new message! Check in the app.";
title = Tutil;
};
sound = default;
}, AnyHashable("google.c.a.e"): 1, AnyHashable("body"): You have new message! Check in the app., AnyHashable("user_photo"): images/3.png, AnyHashable("type"): message, AnyHashable("gcm.message_id"): 1604897386664267, AnyHashable("title"): Tutil, AnyHashable("user_name"): Mali, AnyHashable("lessons"): []]
Below is my code
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
print("%#", userInfo)
// Print message ID.
let content = notification.request.content
let badgeCount = content.badge as! Int //<<i am getting my error here as variables may not be available
incrementBadgeNumberBy(badgeNumberIncrement: badgeCount)
completionHandler([.alert,.badge,.sound])
}
You can get badge like this
if let aps = userInfo["aps"] as? Dictionary<String, Any>, let badge = aps["badge"] as? Int {
print(badge)
}

Trying to customize notifications in macOS with Swift

I am using macOS 10.5.6 and I am trying to display a custom notification. I am using UNNotificationAction to set up a drop down menu for the notification and UNNotificationCategory to save it. I can get the notification correctly. The title and body are displayed but the popup menu for the notification is displayed under a button labeled "Actions".
What I would like to happen is have the label "Actions" changed to a two button format the way that the Reminders app does. I have spent a couple of days searching this web site and several others trying to find the answer but all I have found is the method I am currently using to set up the notification with out the button format that I would like to display. I know that it can be done I just do not know which key words to use to get the answer I would appreciate any help I can get.
enter image description here
Sample notifications
A notification with an attachment:
A notification with an attachment, mouse is hovering over to make the action buttons visible (they're visible right away if there's no attachment).
Sample project
Delegate
AppDelegate is going to handle notifications in the following sample project. We have to make it conform to the UNUserNotificationCenterDelegate protocol.
import UserNotifications
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate {
...
}
We have to set the UNUserNotificationCenter.delegate to our AppDelegate in order to receive notifications. It must be done in the applicationDidFinishLaunching: method.
func applicationDidFinishLaunching(_ aNotification: Notification) {
setupNotificationCategories() // See below
UNUserNotificationCenter.current().delegate = self
// Other stuff
}
Authorization, capabilities, ... omitted for simplicity.
Constants
An example how to avoid hardcoded constant.
enum Note {
enum Action: String {
case acceptInvitation = "ACCEPT_INVITATION"
case declineInvitation = "DECLINE_INVITATION"
var title: String {
switch self {
case .acceptInvitation:
return "Accept"
case .declineInvitation:
return "Decline"
}
}
}
enum Category: String, CaseIterable {
case meetingInvitation = "MEETING_INVITATION"
var availableActions: [Action] {
switch self {
case .meetingInvitation:
return [.acceptInvitation, .declineInvitation]
}
}
}
enum UserInfo: String {
case meetingId = "MEETING_ID"
case userId = "USER_ID"
}
}
Setup categories
Make the notification center aware of our custom categories and actions. Call this function in the applicationDidFinishLaunching:.
func setupNotificationCategories() {
let categories: [UNNotificationCategory] = Note.Category.allCases
.map {
let actions = $0.availableActions
.map { UNNotificationAction(identifier: $0.rawValue, title: $0.title, options: [.foreground]) }
return UNNotificationCategory(identifier: $0.rawValue,
actions: actions,
intentIdentifiers: [],
hiddenPreviewsBodyPlaceholder: "",
options: .customDismissAction)
}
UNUserNotificationCenter.current().setNotificationCategories(Set(categories))
}
Create a notification content
Sample notification content with an attachment. If we fail to create an
attachment we will continue without it.
func sampleNotificationContent() -> UNNotificationContent {
let content = UNMutableNotificationContent()
content.title = "Hey Jim! Weekly Staff Meeting"
content.body = "Every Tuesday at 2pm"
content.userInfo = [
Note.UserInfo.meetingId.rawValue: "123",
Note.UserInfo.userId.rawValue: "456"
]
content.categoryIdentifier = Note.Category.meetingInvitation.rawValue
// https://developer.apple.com/documentation/usernotifications/unnotificationattachment/1649987-init
//
// The URL of the file you want to attach to the notification. The URL must be a file
// URL and the file must be readable by the current process. This parameter must not be nil.
//
// IOW We can't use image from the assets catalog. You have to add an image to your project
// as a resource outside of assets catalog.
if let url = Bundle.main.url(forResource: "jim#2x", withExtension: "png"),
let attachment = try? UNNotificationAttachment(identifier: "", url: url, options: nil) {
content.attachments = [attachment]
}
return content
}
Important: you can't use an image from the assets catalog, because you need an URL pointing to a file readable by the current process.
Trigger helper
Helper to create a trigger which will fire a notification in seconds seconds.
func triggerIn(seconds: Int) -> UNNotificationTrigger {
let currentSecond = Calendar.current.component(.second, from: Date())
var dateComponents = DateComponents()
dateComponents.calendar = Calendar.current
dateComponents.second = (currentSecond + seconds) % 60
return UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
}
Notification request
let content = sampleNotificationContent()
let trigger = triggerIn(seconds: 5)
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error) in
if error != nil {
print("Failed to add a notification request: \(String(describing: error))")
}
}
Handle notifications
Following functions are implemented in the sample project AppDelegate.
Background
This is called when your application is in the background (or even if your application is running, see Foreground below).
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler:
#escaping () -> Void) {
guard let action = Note.Action(rawValue: response.actionIdentifier) else {
print("Unknown response action: \(response.actionIdentifier)")
completionHandler()
return
}
let userInfo = response.notification.request.content.userInfo
guard let meetingId = userInfo[Note.UserInfo.meetingId.rawValue] as? String,
let userId = userInfo[Note.UserInfo.userId.rawValue] as? String else {
print("Missing or malformed user info: \(userInfo)")
completionHandler()
return
}
print("Notification response: \(action) meetingId: \(meetingId) userId: \(userId)")
completionHandler()
}
Foreground
This is called when the application is in the foreground. You can handle the notification silently or you can just show it (this is what the code below does).
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
#escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .badge, .sound])
}
iOS customization
There's another way how to customize the appearance of notifications, but this is not available on the macOS. You have to use attachments.

Why i get error 7014 when trying Sign in with Apple only on tvOS target

i have an application with 2 different target: one for iOS and one for tvOS. We need to add Sign in with Apple functionality so i wrote an object to manage that and use it on both target. When i try to login on iOS target it works perfectly but when i try to use it on tvOS target, i always get the error Code=-7014.
Here some code.
/// Execute apple login, asking for user first and last name and user e-mail
private func appleLogin() {
if #available(iOS 13, tvOS 13, *) {
let request = ASAuthorizationAppleIDProvider().createRequest()
request.requestedScopes = [.fullName, .email]
request.requestedOperation = .operationLogin
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = _presentingViewController as? ASAuthorizationControllerPresentationContextProviding
controller.performRequests()
}
}
And delegate method to manage Apple response
//MARK: - Sing in with Apple
#available(iOS 13.0, tvOS 13.0, *)
extension VVAuthenticationManager: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
LOGI("Apple complete authorization")
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
LOGI("I have user credential")
if let mail = appleIDCredential.email, let authCode = appleIDCredential.authorizationCode, let authStringCode = String(data: authCode, encoding: .utf8) {
LOGI("TOKEN: \(authStringCode)")
// API Registration here
}
default:
// Custom manage error here
break
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
LOGE("Error: \(error.localizedDescription) code: \(error.asAFError?.responseCode ?? 0)")
if let e = error as? ASAuthorizationError {
LOGP("code \(e.code.rawValue)")
LOGP("code \(e.localizedDescription)")
e.userInfo.keys.forEach { (body) in
LOGI("BODY \(body)")
}
e.errorUserInfo.keys.forEach { (body) in
LOGI("BODY ERR \(body)")
}
switch e.code {
case .failed:
LOGP("Failed")
case .canceled:
LOGP("Canceled")
case .invalidResponse:
LOGP("Invalid Response")
case .notHandled:
LOGP("Not Handled")
case .unknown:
LOGP("Unknow error code")
default:
LOGD("NOT RECOGNIZED")
}
}
}
}
Everytime i try to login on tvOS delegate method
authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error)
is called and i get an unknown error (code 1000). Any suggestion?
Thanks in advance
i had a similar problem in past and the issue was on simulator. Sign in with Apple required a 2 factor authentication account and, when try it on Apple TV the second phase of authentication will continue on an iPhone or an iPad.
You should try to run your app on a phisic Apple Tv and sign in with an account active on an iPhone or iPad.

FCM: Message sent but not received

tried to solve this myself. spent like an hour or so still no result.
Got me old code of a previous project regarding FCM. however the code was and still working on it's app. though i managed to transfer the code to my new project. but it won't work here.
Now i know APN's are weird and complicated. but it's more of a memorized situation for me.
Things i have done:
- Uploaded my personal .p12 to my firebase project
- Enabled "Push Notifications" in app capabilities
- Imported and used UserNotifications framework on appdelegate.swift
Here's how my AppDelegate look like:
import UIKit
import Firebase
import FirebaseFirestore
import StoreKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
var window: UIWindow?
override init() {
super.init()
FirebaseApp.configure()
// not really needed unless you really need it FIRDatabase.database().persistenceEnabled = true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
Auth.auth().signInAnonymously() { (authResult, error) in
// ...
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
}
Messaging.messaging().delegate = self
UIApplication.shared.statusBarStyle = .default
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = mainStoryboard.instantiateViewController(withIdentifier: "gateway") as! gatewayViewController
window!.rootViewController = viewController
return true
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
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
// 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)
}
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
// 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)
completionHandler(UIBackgroundFetchResult.newData)
}
Okay so with this code you get the devices Firebase registration token, i copied my code and used it on Cloud Functions to send a test message, here's how my CF looks like:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.helloWorld = functions.https.onRequest((request, response) => {
var registrationToken = 'f8fWx_sANVM:APA91bEd46drxiBvHLZd5YKVClQr91oubzJKOyXE1LNgxOsi3ihUw31yEJL6prHKm-A83B1N1sr2GOff3P9tUsRNhCpG7_VMRlDUDfthIcwkDUgzKPV5NZtlo6pcpxsvD9ZgYlPqibNp';
var payload = {
notification: {
title: "just published new Word",
body: "Hii",
}
};
// registration token.
admin.messaging().sendToDevice(registrationToken, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
return console.log("Successfully sent message:", response);
})
.catch(function(error) {
console.log("Error sending message:", error);
});
});
Okay so far after hitting the helloWorld url, my console gets this:
Successfully sent message: { results: [ { messageId: '0:1537714204565821%b3b8835bb3b8835b' } ],
canonicalRegistrationTokenCount: 0,
failureCount: 0,
successCount: 1,
multicastId: 8154206809408282000 }
Function execution took 60002 ms, finished with status: 'timeout'
Last time on my previous project it took 20ms at it's very best. I still can't figure this out. Your help is greatly appreciated
You're using a HTTP(S) triggered Cloud Function, which means your code must send a response. Since your code doesn't do that, the function runs for 60s and then gets terminated by the Cloud Functions environment. This means you pay for more time than you actually need, so you'll want to fix it.
For example:
// registration token.
admin.messaging().sendToDevice(registrationToken, payload)
.then(function(response) {
// See the MessagingDevicesResponse reference documentation for
// the contents of response.
//return console.log("Successfully sent message:", response);
res.status(200).send(response);
})
.catch(function(error) {
console.log("Error sending message:", error);
});
This will get rid of the message in the Cloud Functions logs, and ensures you only pay for the time you actually need.

Debugging advice for WatchOS2

I've been going through the examples in WatchOS 2 By Tutorial book by the team over at RayWenderlich, specifically chapter 18. They all work fine. In my own App, I am trying to send a button press from the watch to fire a button on the iPhone App. Here's the relevant code in Swift from the Watch and the Phone:
Watch:
//
// InterfaceController.swift
// Wasted Time Extension
//
// Created by Michael Rowe on 7/21/15.
// Copyright © 2010-2015 Michael Rowe. All rights reserved.
//
import WatchKit
import WatchConnectivity
import Foundation
class InterfaceController: WKInterfaceController,WCSessionDelegate {
#IBOutlet var wasteLabel: WKInterfaceLabel!
#IBOutlet var costLabel: WKInterfaceLabel!
#IBOutlet var counter: WKInterfaceLabel!
#IBOutlet var statusButton: WKInterfaceButton!
// our watchconnective session
var session : WCSession?
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
if(WCSession.isSupported()){
session = WCSession.defaultSession()
session!.delegate = self
session!.activateSession()
}
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
func session(session: WCSession, didReceiveMessage message: [String: AnyObject], replyHandler: [String: AnyObject] -> Void) {
print("Did receive message Watch \(message)")
}
#IBAction func addButtonPressed() {
// Pull values from the Phone for current meeting cost, waste costs, and people in meeting
let prefs:NSUserDefaults = NSUserDefaults(suiteName: "a.b.c")!
var counterd = prefs.doubleForKey("keyPeopleInMeeting")
counterd++
counter.setText(String(format:"%9.0f", counterd))
// Sending data to iPhone via Interactive Messaging
if WCSession.isSupported(){
// we have a watch supporting iPhone
let session = WCSession.defaultSession()
// we can reach the watch
if session.reachable {
let message = ["add": "1"]
print("Message \(message)")
session.transferUserInfo(message)
print("Send Message Add - People \(counterd)")
}
}
if WCSession.isSupported() {
let session = WCSession.defaultSession()
if session.reachable {
let message = ["add":"1"]
session.sendMessage(message, replyHandler: { ( reply: [String: AnyObject]) -> Void in
print("Reply: \(reply)")
}, errorHandler: { (error: NSError) -> Void in
print("ERROR Watch: \(error.localizedDescription)")
})
} else { // reachable
self.showReachabilityError()
}
}
print("Watch Add Button Pressed \(counterd)")
}
#IBAction func minusButtonPressed() {
// Pull values from the Phone for current meeting cost, waste costs, and people in meeting
let prefs:NSUserDefaults = NSUserDefaults(suiteName: "a.b.c")!
var counterd = prefs.doubleForKey("keyPeopleInMeeting")
counterd--
if (counterd <= 1) {
counterd = 1
}
counter.setText(String(format:"%9.0f", counterd))
if WCSession.isSupported() {
let session = WCSession.defaultSession()
if session.reachable {
let message = ["minus":"1"]
session.sendMessage(message, replyHandler: { ( reply: [String: AnyObject]) -> Void in
print("Reply: \(reply)")
}, errorHandler: { (error: NSError) -> Void in
print("ERROR Watch: \(error.localizedDescription)")
})
} else { // reachable
self.showReachabilityError()
}
}
print("Watch Minus Button Pressed \(counterd)")
}
func statusButtonPressed() {
// Pull values from the Phone for current meeting cost, waste costs, and people in meeting
let prefs:NSUserDefaults = NSUserDefaults(suiteName: "a.b.c")!
let status = statusButton.description
if WCSession.isSupported() {
let session = WCSession.defaultSession()
if session.reachable {
let message = ["status":status]
session.sendMessage(message, replyHandler: { ( reply: [String: AnyObject]) -> Void in
print("Reply: \(reply)")
}, errorHandler: { (error: NSError) -> Void in
print("ERROR Watch: \(error.localizedDescription)")
})
} else { // reachable
self.showReachabilityError()
}
}
print("Watch Status Button Pressed - Status \(statusButton)")
}
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]){
let prefs:NSUserDefaults = NSUserDefaults(suiteName: "a.b.c")!
if let waste = applicationContext["waste"] as? Float {
print("Watch Receive - Waste \(waste)")
}
if let cost = applicationContext["cost"] as? Float {
print("Watch Receive - Cost \(cost)")
}
if let counternum = applicationContext["counter"] as? Float {
print("Watch Receive - Counter \(counternum)")
}
if let status = applicationContext["status"] as? String {
print("Watch Receive - Status \(status)")
statusButton.setTitle(status)
}
}
private func showReachabilityError() {
let tryAgain = WKAlertAction(title: "Try Again", style: .Default, handler: { () -> Void in })
let cancel = WKAlertAction(title: "Cancel", style: .Cancel, handler: { () -> Void in })
self.presentAlertControllerWithTitle("Your iPhone is not reachable.", message: "You cannot adjust the status or number of attendees Watch is not currently connected to your iPhone. Please ensure your iPhone is on and within range of your Watch.", preferredStyle: WKAlertControllerStyle.Alert, actions:[tryAgain, cancel])
}
func session(session: WCSession, didFinishUserInfoTransfer userInfoTransfer: WCSessionUserInfoTransfer, error: NSError?) {
print("Transfer User Info Error watch: \(error)")
}
}
And the receiving code on the
iPhone:CODE:
func session(session: WCSession,
didReceiveMessage message: [String : AnyObject],
replyHandler: ([String : AnyObject]) -> Void) {
if let counterd = message["add"] as? Float {
let reply = ["add":counterd]
print("iPhone Receive Add \(counterd)")
addButtonPressed(self)
replyHandler(reply)
}
if let counterd = message["minus"] as? Float {
let reply = ["minus":counterd]
print("iPhone Receive minus \(counterd)")
removeButtonPressed(self)
replyHandler(reply)
}
if let status = message["status"] as? String {
if status == "Start" {
let reply = ["status":"Quorum"]
meetingStartedButtonPressed(self)
replyHandler(reply)
}
if status == "Quorum" {
let reply = ["status": "Finish"]
quorumButtonPressed(self)
replyHandler(reply)
}
if status == "Finish" {
let reply = ["status": "Reset"]
meetingEndedButtonPressed(self)
replyHandler(reply)
}
if status == "Reset" {
let reply = ["status": "Start"]
resetButtonPressed(self)
replyHandler(reply)
}
print("iPhone Received Status Button \(status)")
}
}
I get the messages firing fine on the Watch and see them in the debug log... But they do not seem to fire on the Phone. The phone is successfully sending its messages to the watch.
I have tested this code both in the simulator and on my own watch and iPhone. Note that the messages from the iPhone to the Watch are done using the via updateApplicationContext vs. the send message I am trying to use to send messages from the watch to the iPhone. Here's a sample of the iPhone code for sending context:
if WCSession.isSupported() {
if session.watchAppInstalled {
let UserInfo = ["waste":Float((wastedAmount.text! as NSString).floatValue), "cost":Float((totalAmount.text! as NSString).floatValue), "counter":Float((peopleInMeeting.text! as NSString).floatValue), "status":"Start"]
do {
try session.updateApplicationContext(UserInfo as! [String : AnyObject])
} catch {
print("Updating the context failed: ")
}
}
}
More information is needed regarding specifically what you're actually seeing on the Watch, when you say:
I get the messages firing fine on the Watch and see them in the debug log... But they do not seem to fire on the Phone. The phone is successfully sending its messages to the watch.
However, one common occurrence is that the iPhone code is actually working correctly, and the only thing you are not seeing is the debug statements printed to the console. This seems likely to be the case since you say you are seeing the expected Watch messages, presumably including those from print("Reply: \(reply)"). This indicates the message is being handled by the iPhone.
When that's the case, it's often simply that you are expecting to see debug console messages from both the Watch and iOS simulator processes at the same time, but in fact you're only connected to one or the other. There are (at least) two things you can do here:
Run the WatchKit app from Xcode, but then change to attach to the iPhone process instead. In Xcode, go Debug > Attach to Process... and select the iPhone app under "Likely Targets".
Start by running the iPhone app, which will mean you are already attached to that process. In the Apple Watch simulator, run the Watch app. You'll then be able to debug the iPhone side of the communication.
To debug in Watch-OS while running iPhone app and vice versa in Xcode-8.1. Required running Process need to be attached .
Visually:-