Working on a personal project and trying to receive content from an Apple Watch. The data being sent is fine but I get no response from either the phone or the watch.
Inside AppDelegate I have a session started inside didFinishLaunchingWithOptions and it conforms to WCSessionDelegate.
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
var replyValues = [String : Any]()
switch message["command"] as! String {
case "logAttempt":
print("Log attempt")
replyValues["response"] = "true"
case "getData":
print("Get course data")
replyValues["response"] = "true"
default:
break
}
replyHandler(replyValues)
Inside the WatchKit class it also conforms to WCSessionDelegate and has the following to send message to the phone.
session.sendMessage(data, replyHandler: { replyHandler in
print(replyHandler)
if let response = replyHandler["response"] as? String {
print("app: Response from app to watch: \(response)")
} else {
print("error: Somethings wrong!: \(replyHandler["response"])")
}
}) { error in
print("Error: \(error.localizedDescription)")
}
ExtensionDelegate.swift file also conforms to WCSessionDelegate and starts a session inside applicationDidFinishLaunching. Struggling to get this working.
Thanks!
Edit: Setting up watch inside appDel:
fileprivate func setupWatch() {
if WCSession.isSupported() {
session = WCSession.default
session!.delegate = self
session!.activate()
}
}
Related
******** Session Activated ******
Watch is paired true
Watch is reachable false
Watch app installed false
I have established the session but not able to make connection reachability between the iOS app (UIKit) and watchOS app (swiftUI)
This is my watchConnectivity class
import WatchConnectivity
class WatchSessionManager: NSObject, WCSessionDelegate {
static let sharedManager = WatchSessionManager()
public var watchDelegate: WCSessionDelegate?
private override init() {
super.init()
}
private let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil
#available(iOS 9.3, *)
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
switch activationState {
case .activated:
print("WCSession activated successfully")
case .inactive:
print("Unable to activate the WCSession. Error: \(error?.localizedDescription ?? "--")")
case .notActivated:
print("Unexpected .notActivated state received after trying to activate the WCSession")
#unknown default:
print("Unexpected state received after trying to activate the WCSession")
}
// Handle activation completion
print("jew")
if session.isReachable {
let message = ["message": "Hello from iPhone"]
session.sendMessage(message, replyHandler: nil) { (error) in
print(error)
}
}else{
print("Not Reachable")
}
#if os(iOS)
print("******** Session Activated ******")
print("Watch is paired \(session.isPaired)")
print("Watch is reachable \(session.isReachable)")
print("Watch app installed \(session.isWatchAppInstalled)")
#endif
//print("Watch is reachable \(session.isReachable)")
}
#if os(iOS)
func sessionDidBecomeInactive(_ session: WCSession) {
print("Session Inactive")
}
func sessionDidDeactivate(_ session: WCSession) {
print("Session deactivated")
}
#endif
var validSession: WCSession? {
// paired - the user has to have their device paired to the watch
// watchAppInstalled - the user must have your watch app installed
// Note: if the device is paired, but your watch app is not installed
// consider prompting the user to install it for a better experience
#if os(iOS)
if let session = session, session.isPaired && session.isWatchAppInstalled {
print("Watch is paired \(session.isPaired)")
print("Watch app installed \(session.isWatchAppInstalled)")
return session
}
#elseif os(watchOS)
print("Watch is reachable \(session!.isReachable)")
return session
#endif
return nil
}
func startSession() {
session?.delegate = self
session?.activate()
}
}
// MARK: Application Context
// use when your app needs only the latest information
// if the data was not sent, it will be replaced
extension WatchSessionManager {
// Sender
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
guard let content = message["content"] as? String else { return }
DispatchQueue.main.async {
//self.messageLabel.text = content
print(content)
}
}
func updateApplicationContext(applicationContext: [String : AnyObject]) throws {
if let session = validSession {
do {
print("WCSession contect sent \(applicationContext)")
try session.updateApplicationContext(applicationContext)
print("WCSession contect sent \(applicationContext)")
} catch let error {
throw error
}
}else{
print("Error sending context")
}
}
// Receiver
// func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
// // handle receiving application context
// let isEnabledStatus = applicationContext["isEnabled"] as? Bool
//
// // DispatchQueue.main.async {
// print("Received application context \(applicationContext) 1")
// #if os(watchOS)
// if watchDelegate != nil{
// print("Received application context \(applicationContext) 2")
//
// }
// #endif
// // make sure to put on the main queue to update UI!
// // }
// }
}
// MARK: User Info
// use when your app needs all the data
// FIFO queue
extension WatchSessionManager {
// Sender
func transferUserInfo(userInfo: [String : AnyObject]) -> WCSessionUserInfoTransfer? {
return validSession?.transferUserInfo(userInfo)
}
func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {
// implement this on the sender if you need to confirm that
// the user info did in fact transfer
}
// Receiver
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
// handle receiving user info
DispatchQueue.main.async {
// make sure to put on the main queue to update UI!
}
}
}
// MARK: Transfer File
extension WatchSessionManager {
// Sender
func transferFile(file: NSURL, metadata: [String : AnyObject]) -> WCSessionFileTransfer? {
return validSession?.transferFile(file as URL, metadata: metadata)
}
func session(_ session: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?) {
// handle filed transfer completion
}
// Receiver
func session(_ session: WCSession, didReceive file: WCSessionFile) {
// handle receiving file
DispatchQueue.main.async {
// make sure to put on the main queue to update UI!
}
}
}
// MARK: Interactive Messaging
extension WatchSessionManager {
// Live messaging! App has to be reachable
private var validReachableSession: WCSession? {
if let session = validSession , session.isReachable {
return session
}
return nil
}
// Sender
func sendMessage(message: [String : AnyObject],
replyHandler: (([String : Any]) -> Void)? = nil,
errorHandler: ((Error) -> Void)? = nil)
{
validReachableSession?.sendMessage(message, replyHandler: replyHandler, errorHandler: errorHandler)
}
func sendMessageData(data: Data,
replyHandler: ((Data) -> Void)? = nil,
errorHandler: ((Error) -> Void)? = nil)
{
validReachableSession?.sendMessageData(data, replyHandler: replyHandler, errorHandler: errorHandler)
}
// Receiver
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
// handle receiving message
DispatchQueue.main.async {
// make sure to put on the main queue to update UI!
print("Message Received\(message)")
}
}
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
// handle receiving message data
DispatchQueue.main.async {
// make sure to put on the main queue to update UI!
}
}
}
in ios setting in appdelegate:
WatchSessionManager.sharedManager.startSession() in didFinishLaunchingWithOptions
in watchOS
.onAppear {
WatchSessionManager.sharedManager.startSession()
}
I tried to pass the data between my iOS app and watchOS app
private func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : String]) -> Void) {
replyHandler(["message": "Hello Watch!"])
}
I'm currently building an Apple Watch Companion App with Swift and Flutter. I'm doing this with the help of theamorn's Github project. Everything works in the simulator (iOS 15.0 and WatchOS 8.0), even if the iOS App is force quit. However, when testing on my AW Series 3 (WatchOS 8.0) and iPhone 11 (iOS 15.0) it will only work, as long as the iOS App is opened.
My AppDelegate.swift of the iOS App
import UIKit
import Flutter
import WatchConnectivity
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
var session: WCSession?
let methodChannelName: String = "app.controller.watch"
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
initFlutterChannel()
if WCSession.isSupported() {
session = WCSession.default;
session!.delegate = self;
session!.activate();
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func initFlutterChannel() {
if let controller = window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: methodChannelName,
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler({ [weak self] (
call: FlutterMethodCall,
result: #escaping FlutterResult) -> Void in
switch call.method {
case "flutterToWatch":
guard let watchSession = self?.session, watchSession.isPaired,
watchSession.isReachable, let methodData = call.arguments as? [String: Any],
let method = methodData["method"], let data = methodData["data"] as? Any else {
result(false)
return
}
let watchData: [String: Any] = ["method": method, "data": data]
watchSession.sendMessage(watchData, replyHandler: nil, errorHandler: nil)
result(true)
default:
result(FlutterMethodNotImplemented)
}
})
}
}
}
extension AppDelegate: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func sessionReachabilityDidChange(_ session: WCSession) {
print("Watch reachability: \(session.isReachable)")
if (session.isReachable) {
//invoke sendWakeupToFlutter via MethodChannel when reachability is true
DispatchQueue.main.async {
if let controller = self.window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: self.methodChannelName,
binaryMessenger: controller.binaryMessenger)
channel.invokeMethod("sendWakeupToFlutter", arguments: [])
}
}
}
}
func sessionDidBecomeInactive(_ session: WCSession) {
}
func sessionDidDeactivate(_ session: WCSession) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
DispatchQueue.main.async {
if let method = message["method"] as? String, let controller = self.window?.rootViewController as? FlutterViewController {
let channel = FlutterMethodChannel(
name: self.methodChannelName,
binaryMessenger: controller.binaryMessenger)
channel.invokeMethod(method, arguments: message)
}
}
}
}
My WatchViewModel.swift in my Watch Extension
import Foundation
import WatchConnectivity
class WatchViewModel: NSObject, ObservableObject {
var session: WCSession
var deviceList: String = ""
#Published var loading: Bool = false
#Published var pubDeviceList: [Device]?
// Add more cases if you have more receive method
enum WatchReceiveMethod: String {
case sendLoadingStateToNative
case sendSSEDeviceListToNative
}
// Add more cases if you have more sending method
enum WatchSendMethod: String {
case sendWakeupToFlutter
case sendCloseToFlutter
}
init(session: WCSession = .default) {
self.session = session
super.init()
self.session.delegate = self
session.activate()
}
func sendDataMessage(for method: WatchSendMethod, data: [String: Any] = [:]) {
sendMessage(for: method.rawValue, data: data)
}
}
extension WatchViewModel: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func sessionReachabilityDidChange(_ session: WCSession) {
print("iPhone reachability: \(session.isReachable)")
if(session.isReachable) {
//invoke sendWakeupToFlutter via sendMessage when reachability is true
sendDataMessage(for: .sendWakeupToFlutter)
}
}
// Receive message From AppDelegate.swift that send from iOS devices
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
DispatchQueue.main.sync {
guard let method = message["method"] as? String, let enumMethod = WatchReceiveMethod(rawValue: method) else {
return
}
switch enumMethod {
case .sendLoadingStateToNative:
self.loading = (message["data"] as? Bool) ?? false
case .sendSSEDeviceListToNative:
self.deviceList = (message["data"] as? String) ?? ""
let data = self.deviceList.data(using: .utf8)!
do {
self.pubDeviceList = try JSONDecoder().decode([Device].self, from: data)
} catch let error {
print(error)
}
}
}
}
func sendMessage(for method: String, data: [String: Any] = [:]) {
guard session.isReachable else {
print("ios not reachable")
return
}
print("ios is reachable")
let messageData: [String: Any] = ["method": method, "data": data]
let callDepth = 10
session.sendMessage(messageData, replyHandler: nil, errorHandler: nil)
}
}
Does someone know how to fix this? Thanks in advance!
EDIT:
The change in my watch extension so far:
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if (activationState == WCSessionActivationState.activated) {
sendDataMessage(for: .sendWakeupToFlutter)
}
}
NOTE: The accepted answer didn't solve my problem entirely, but improved the situation. I ended up making a rather independent Watch App w/o using Flutter MethodChannels.
It probably won't fix your issue but you should be careful about using session.isReachable - its value is only valid for a session that is activated, which it almost certainly is (activate session is async but quick I think), but WatchOS has a number of APIs where the value can only be trusted if certain conditions are met, and you should check, otherwise you end up with a value like true or false when the real value should be 'don't know'
IIRC isReachable is normally true from watch to counterpart iPhone app, you should consider sending the wake up proactively when the session activates.
tl;dr It should probably work when the quit app was built for release.
Long Version
I had the same issue. What I found out is that it works fine when building the flutter app in release mode (rather than just running it via XCode/non release mode).
The reason for that is that, when opening a quit app that wasn't built for release, you'll see the screen that you need to run it via a Flutter IDE or XCode. Which indicates that the app is not properly launched when trying to launch a non-release app via the home screen. That is why the message that is being sent from the watch will probably not reach the iOS WCSession or make it through the platform channel.
I'm working on a watch app. I have to send data from the mobile to watch.
This is working properly.
The main problem occurs when I'm sending message from watch to mobile app because I want that the communication to be initiated by watch app. I have implemented the method to send data from watch to mobile app but always getting this
"WCErrorDomain Code=7014 "Payload could not be delivered." error.
I'm using xcode 7.3 and watchOS version is 2.0.
One more thing: is it possible that the sendMessage method opens the mobile app in background using watch connectivity framework?? Or if anyone knows any other way to open the mobile app in background then please suggest me. Thanks in advance.
session.sendMessage(message, replyHandler: { reply in
}, errorHandler: { error in
print("error: \(error)")
})
EDIT
In AppDelegate and ExtensionDelegate:
override init() {
super.init()
setupWatchConnectivity()
}
private func setupWatchConnectivity() {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
In ExtensionDelegate:
func sendMessage(){
let session = WCSession.defaultSession()
let applicationData:[String:AnyObject] = ["text":"test", "badgeValue": 100 ]
WatchSessionManager.sharedManager.sendMessage(applicationData, replyHandler: {replyMessage in
print("reply received from iphone")
}, errorHandler: {(error ) -> Void in
// catch any errors here
print("no reply message from phone")
})
}
In AppDelegate:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
let text = message["text"] as! String
let badgeValue = message["badgeValue"] as! Int
dispatch_async(dispatch_get_main_queue()) { () -> Void in
print("iphone received message from watch App")
self.sendNotification(text, badgeValue: badgeValue)
let applicationDict = ["wake": "nowAwake"]
replyHandler(applicationDict as [String : String])
}
}
In watchSessionManager :
func sendMessage(message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)?, errorHandler: ((NSError) -> Void)? = nil){
session.sendMessage(message, replyHandler: { reply in
}, errorHandler: { error in
print("error: \(error)")
})
print("mesage %#",message)
}
I'm implementing simple one-line communication between iOS app and watchKit with sendMessage.
The issue is - the communication is continuous.
The user presses a button on appleWatch, same action happens on Phone e.t.c.
Unfortunately, after two or three actions - the iOS app stops responding.
As advised by some on the forums - i've implemented backgroundTaskWithExpirationHandler, however, it doesn't work (doesn't give me even a promised three minute timeout).
Here's my code in the iOS app AppDelegate
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//recieve messages from watch
print(message["b"]! as? String)
let sweetN = message["b"]! as? String
//var sweetB = message["abs"]! as? [Int : Bool]
dispatch_async(dispatch_get_main_queue(), {
let taskID = self.beginBackgroundUpdateTask()
if sweetN == "peeks"{
if WCSession.isSupported(){
let message = [ "gettingData": "datareceived" ]
session.sendMessage(message, replyHandler: { replyDict in }, errorHandler: { error in })
}
}
self.endBackgroundUpdateTask(taskID)
})
}
func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}
func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
This worked for me.
You have to set the WCSession in the app delegate like:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
replyHandler(["message": "received!"])
}
and in the apple watch: the file extension delegate.swift do the same:
override init() {
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
So I am using Watch Connectivity to request an array from the iPhone to the Watch.
The idea was to sendMessage from the watch, and the iPhone will reply with the array within the didReceiveMessage method.
However the iPhone does not seem to be responding, I thought the iPhone would open the application when I send the message from the Watch. I have tried even opening the application when I sendMessage but still no luck. When I wait long enough I get the following error message:
Error Domain=WCErrorDomain Code=7012 "Message reply took too long."
UserInfo={NSLocalizedDescription=Message reply took too long.,
NSLocalizedFailureReason=Reply timeout occured.}
Does anybody know where I may be going wrong ?
Apple Watch
import WatchKit
import Foundation
import CoreData
import WatchConnectivity
class BookmarkedInterfaceController: WKInterfaceController, WCSessionDelegate {
var session : WCSession!
var objects: [AnyObject]!
#IBOutlet var table: WKInterfaceTable!
override func willActivate() {
super.willActivate()
//Check if session is supported and Activate
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
sendMessageToIphone()
}
func sendMessageToIphone() {
if WCSession.defaultSession().reachable {
print("WCSession is reachabe")
let messageDict = ["Request": "iPhone Can You Give Me The Array"]
WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
print(replyDict)
}, errorHandler: { (error) -> Void in
print(error)
})
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//recieving message from iphone
print("recieved message from iphone \(message)")
objects.append(message["Array"]!)
print("Objects array = \(objects)")
}
The console outputs
WCSession is reachabe
Array nil
iPhone App Delegate
import UIKit
import CoreData
import WatchConnectivity
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
var window: UIWindow?
var session : WCSession!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//Check if session is supported and Activate
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print("did recieve message from Watch")
let applicationData = ["Array":["One", "Two", "Three"]]
replyHandler(applicationData)
}
Nothing from the iPhone is being executed. Even when I manually open the app.
If you want the reply to the message the watch sent to contain the requested data, you should change your code to the following:
Watch
func sendMessageToIphone() {
if WCSession.defaultSession().reachable {
print("WCSession is reachabe")
let messageDict = ["Request": "iPhone Can You Give Me The Array"]
WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
print("Array \(replyDict["array"])")
}, errorHandler: { (error) -> Void in
print(error)
})
}
}
Phone
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print("did recieve message from Watch")
let applicationData = ["Array":["One", "Two", "Three"]]
//If identifier from recievedMessage is for Objects
replyHandler(applicationData)
}
And separately, the reason why the sendMessage from the phone is not received by the watch is because you've implemented the wrong delegate method for the sendMessage invocation you are using.
If you call sendMessage with a nil replyHandler then this delegate method will be invoked on the receiving side:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject])
If you call sendMessage with a non-nil replyHandler then this delegate method will be invoked on the receiving side:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
You must activate the session before sending the message .Also you must set the delegate before you activate the session because you may lose some pending messages.
iphone side :
import UIKit
import WatchConnectivity
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
var window: UIWindow?
var session : WCSession!
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print("did recieve message from Watch")
let applicationData = ["Array":["One", "Two", "Three"]]
//If identifier from recievedMessage is for Objects
session.sendMessage(applicationData, replyHandler: { reply in
print("Got reply: \(reply)")
}, errorHandler: { error in
print("error: \(error)")
})
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
return true
}
}
iWatch extension InterfaceController
You must activate the session in the interface controller willactivate method.
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
var session : WCSession!
var objects: [AnyObject]!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
objects = []
sendMessageToIphone()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
func sendMessageToIphone() {
if WCSession.defaultSession().reachable{
print("WCSession is reachabe")
let messageDict = ["Request": "iPhone Can You Give Me The Array"]
WCSession.defaultSession().sendMessage(messageDict, replyHandler: { (replyDict) -> Void in
print(replyDict)
}, errorHandler: { (error) -> Void in
print(error)
})
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//recieving message from iphone
print("recieved message from iphone \(message)")
objects.append(message["Array"]!)
print("Objects array = \(objects)")
}
}
Note. Run the iphone application first. Then Run the extension and keep the iphone app in foreground.