Turn transferUserInfo into sendMessage - ios

I've currently got my data being passed from the iPhone to the Watch using transferUserInfo.
I would like to add an additional function to ask the iPhone for the same data (immediately since it could be the first time the Watch is opened) from the Watch.
So I would needsendMessage:replyHandler:errorHandler:, but I can't figure out how to use it.
(What I have with transferUserInfo:)
iPhone TableViewController.swift:
let applicationDict = ["TC" : newP.tC, "Mat" : newP.mat]
let transfer = WCSession.defaultSession().transferUserInfo(applicationDict)
Watch InterfaceController.swift:
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let tC = userInfo["TC"] as? String, let mat = userInfo["Mat"] as? String {
receivedData.append(["TC" : tC , "Mat" : mat])
ExtensionDelegate.evnts.append(Evnt(dataDictionary: ["TC" : tC , "Mat" : mat]))
doTable()
} else {
print("tC and mat are not same as dictionary value")
}
}
(What I have tried adding with sendMessage:)
iPhone TableViewController.swift:
let msg = WCSession.defaultSession().sendMessage(applicationDict, replyHandler: nil, errorHandler: nil)
Watch InterfaceController.swift:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
//handle received message
let value1 = message["TC"] as? String
let value2 = message["Mat"] as? String
//use this to present immediately on the screen
dispatch_async(dispatch_get_main_queue()) {
//self.messageLabel.setText(value)
print("ValWatch1: \(value1)")
print("ValWatch2: \(value2)")
}
//send a reply
replyHandler(["TC":"Yes"])
}
I can't tell what all I have messed up.

The code you are trying to send a message to the watch is correct
let msg = WCSession.defaultSession().sendMessage(applicationDict, replyHandler: nil, errorHandler: nil)
But are you sure the watch is paired? you may need to add this check first before sending the message
if (WCSession.defaultSession().reachable) {
// send message
}
Also make sure your watch app is running while you are trying to send the message from your parent app. If your watch app is not in the foreground, then it wouldn't receive the message sent from your iphone app.

Related

How to receive a watchOS file transfer on iOS in the background

I am using transferFile and I can successfully send and receive files, but in order to complete the transfer process, I need to open up the iPhone app.
In observing other apps, it appears that they are able to receive and act upon received data in the background (and send a push notification to the user, for example).
I am wondering how they did this.
You should send a message from the watch app to the phone using the sendMessage function of watch connectivity requesting the data. This will wake up the iphone app. Then in your didreceivemessage method on the phone you should use the filetransfer function to send your files to the watch.
To clarify when a message is sent using sendMessage this wakes the iphone application up in the background to receive the message to where it can respond with a file transfer. Hope this helps
You need to send a message first before sending the file transfer. Implement something like this on your watch side
func sendActivationMessage() {
if session.activationState == .activated && session.isReachable {
session.sendMessage(["Watch Message" : "Activate"], replyHandler: {
(reply) in
if reply["Phone Message"] as! String == "Activated" {
//This is where you should implement your file transfer
}
}, errorHandler: { (error) in
print("***** Error Did Occur: \(error) *****")
})
} else {
print("***** Activation Error *****")
}
}
Then in your didreceivemessage function on the phone side implement something like this
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
if let messageFromWatch = message["Watch Message"] {
let messageData = messageFromWatch as! String
//Message From Watch to Activate Watch Connectivity Session
if messageData == "Activate" {
replyHandler(["Phone Message" : "Activated"])
}
}

AVAudioSession always returns the same outputVolume

I have an watchOS app that request the current volume from the iPhone parent app via:
session?.sendMessage(["getVolume":1], replyHandler: {
replyDict in
if let currentVolume = replyDict["currentVolume"] as? Float{
NSLog("Current volume received from phone with value: \(currentVolume)")
}
}, errorHandler: {
(error) -> Void in
NSLog("Error: :\(error)")
// iOS app failed to get message. Send it in the background
//self.session?.transferUserInfo(["getVolume":1])
})
The iPhone app handles it like this:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
NSLog("Received a message")
if let _ = message["getVolume"] as? Int{
replyHandler(["currentVolume":AVAudioSession.sharedInstance().outputVolume])
}
else{
replyHandler([:])
}
}
This always returns the same outputVolume the phone had on the first request.
I investigated several things like if there is some kind of caching for the background request but it is always a new call that returns the new value.
Is there any workaround to get system volume with a different way or maybe a solution how to get this to work with AVAudioSession?
This isn't an issue with the WatchKit or watchOS per se, as I've experienced this in a regular iOS application.
I had to activate my app's audio session in order to observe the change in volume:
try? AVAudioSession.sharedInstance().setActive(true)

WatchConnectivity - using sendMessage

I am trying to establish connectivity between the Apple Watch (version 2.0.1) and my iPhone (running iOS 9.1) with the WatchConnectivity API in Swift.
I followed this tutorial and could not achieve messaging between the devices.
Messaging from the Apple Watch:
let applicationData = ["data":sampleData]
self.wcSession.sendMessage(applicationData, replyHandler: {(_: [String : AnyObject]) -> Void in
// handle reply from iPhone app here
}, errorHandler: {(error ) -> Void in
// catch any errors here
})
In my ViewController.swift:
// MARK: - WatchConnectivity Session
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
let sample:HKQuantitySample = (message["data"] as? HKQuantitySample)!
print("Sample messaged: \(sample)")
}
func sessionReachabilityDidChange(session: WCSession) {
print("session reachability changed: \(session.reachable)")
}
Both Watch app and the iOS app are foreground!!
I am not sure what is missing.
All of the WCSession APIs that take a dictionary as a parameter only accept dictionaries of property list types; this includes the sendMessage API you are using:
message / A dictionary of property list values that you want to send. You define the contents of the dictionary that your counterpart supports. This parameter must not be nil.
So HKSamples are not a property list type which is why this isn't working, although you are saying the error handler is not getting invoked which sounds very suspicious. Are you certain changing your code to this doesn't log anything?
self.wcSession.sendMessage(applicationData, replyHandler: {(_: [String : AnyObject]) -> Void in
// handle reply from iPhone app here
}, errorHandler: {(error ) -> Void in
print(error);
})

WCSession.sendMessage works 50/50

Lately, I am working on a project is related to Watch/iPhone communication again. But my code works sometimes and doesn’t work sometimes which is kind of weird to me because I think the code should either work or not. It cannot be 50/50. Therefore, I have no idea what goes wrong.
setup WCSession on iPhone:
class WatchCommunicationController: NSObject, WCSessionDelegate {
var session : WCSession?
override init(){
// super class init
super.init()
// if WCSession is supported
if WCSession.isSupported() { // it is supported
// get default session
session = WCSession.defaultSession()
// set delegate
session!.delegate = self
// activate session
session!.activateSession()
} else {
print("iPhone does not support WCSession")
}
}
... ...
}
similar WCSession setup on Watch:
class PhoneCommunicationController: NSObject, WCSessionDelegate {
var session : WCSession?
override init(){
// super class init
super.init()
// if WCSession is supported
if WCSession.isSupported() { // it is supported
// get default session
session = WCSession.defaultSession()
// set delegate
session!.delegate = self
// activate session
session!.activateSession()
} else {
print("Watch does not support WCSession")
}
}
... ...
}
send out message on Watch:
func sendGesture(gesture : GKGesture){
// if WCSession is reachable
if session!.reachable { // it is reachable
// create the interactive message with gesture
let message : [String : AnyObject]
message = [
"Type":"Gesture",
"Content":gesture.rawValue
]
// send message
session!.sendMessage(message, replyHandler: nil, errorHandler: nil)
print("Watch send gesture \(gesture)")
} else{ // it is not reachable
print("WCSession is not reachable")
}
}
related enum:
enum GKGesture: Int {
case Push = 0, Left, Right, Up, Down
}
receive message on iPhone:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
//retrieve info
let type = message["Type"] as! String
let content = message["Content"]
switch type {
case "Gesture":
handleGesture(GKGesture(rawValue: content as! Int)!)
default:
print("Received message \(message) is invalid with type of \(type)")
}
}
func handleGesture(gesture : GKGesture){
print("iPhone receives gesture \(gesture)")
var notificationName = ""
switch gesture {
case .Up:
notificationName = "GestureUp"
case .Down:
notificationName = "GestureDown"
case .Left:
notificationName = "GestureLeft"
case .Right:
notificationName = "GestureRight"
case .Push:
notificationName = "GesturePush"
}
NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil)
}
somehow I can’t debug my Watch app on Xcode, the debug session just won’t attach. I don’t know why. Therefore, I debug one-sided with just the iPhone.
sometimes I got "receives gesture” print out, and sometimes not. And the same for getting the notification.
I don't know if Int would be wrapped around to NSNumber while being transfer within WCSession. If it would be, then that must be why when I use Int as the base class of the enum it won't work and works when String is the base class.
Connectivity Known Issue Your app may crash when using NSNumber and
NSDate objects with the WCSession API.
Workaround: Convert an NSNumber or NSDate object to a string before
calling WCSession APIs. Do the opposite conversion on the receiving
side.
Watch OS 2 Beta 4 release note
My guess is your call to sendMessage is returning an error in the cases where it fails, but you haven't implemented the error handler!! For now while you are getting up and running you can get away with just printing the error, but if this is shipping code you really ought to handle the appropriate errors:
// send message
session.sendMessage(message, replyHandler: nil, errorHandler: { (error) -> Void in
print("Watch send gesture \(gesture) failed with error \(error)")
})
print("Watch send gesture \(gesture)")
Your flow is correct but the difficulty is to understand how to debug:
Debug Watch:
Run the iPhone target and when it is done hit the Stop button.
Open the iOS app inside the simulator (run it manually from the simulator and not from Xcode) and let it hang there.
Switch to the Watch target (yourAppName WatchKit App), put the relevant breakpoint and run it.
The iOS app will be put automatically in the background and then you will be able to use sendMessage method (at the Watch target) to send whatever you need and if you have a replayHandler in your iOS app you will even receive the relevant messages inside the sendMessage at your Watch target (i.e InterfaceController)
Small Swift example:
Sending a Dictionary from Watch to iOS app:
if WCSession.defaultSession().reachable == true {
let requestValues = ["Send" : "From iWatch to iPhone"]
let session = WCSession.defaultSession()
session.sendMessage(requestValues,
replyHandler: { (replayDic: [String : AnyObject]) -> Void in
print(replayDic["Send"])
}, errorHandler: { (error: NSError) -> Void in
print(error.description)
})
}
else
{
print("WCSession isn't reachable from iWatch to iPhone")
}
Receiving the message from the Watch and sending a replay from the iOS app:
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print(message.values)
var replyValues = Dictionary<String, AnyObject>()
replyValues["Send"] = "Received from iphone"
// Using the block to send back a message to the Watch
replyHandler(replyValues)
}
Debug iPhone:
The exact opposite of debug watch
Also, the answer by #sharpBaga has an important consideration.

How trigger background process from Watch on iPhone (trigger: Watch)?

I'd like to add to my Watch app functionality which send to iPhone app a Local Notification (while iPhone app is on the background or iPhone is locked).
I know how to create Local Notification itself.
What Im asking for is way, how to trigger background process (which contains also Local Notification) on iPhone by (for example) tapping on button on Apple Watch.
WKInterfaceController.openParentApplication is the official way to communicate with the iPhone. Documentation.
You pass parameters in the userInfo dictionary and retrieve results via the reply block.
On the iPhone the request is handled by appDelegate's handleWatchKitExtensionRequest method. Documentation
Code in my InterfaceController.swift:
#IBAction func btn() {
sendMessageToParentApp("Button tapped")
}
// METHODS #2:
func sendMessageToParentApp (input:String) {
let dictionary = ["message":input]
WKInterfaceController.openParentApplication(dictionary, reply: { (replyDictionary, error) -> Void in
if let castedResponseDictionary = replyDictionary as? [String:String], responseMessage = castedResponseDictionary["message"] {
println(responseMessage)
self.lbl.setText(responseMessage)
}
})
}
Next i made new method in my AppDelegate.swift:
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {
if let infoDictionary = userInfo as? [String:String], message = infoDictionary["message"] {
let response = "iPhone has seen this message." // odešle se string obsahující message (tedy ten String)
let responseDictionary = ["message":response] // tohle zase vyrobí slovník "message":String
NSNotificationCenter.defaultCenter().postNotificationName(notificationWatch, object: nil)
reply(responseDictionary)
}
}
As you can see I use Notification to get iOS app know that button has been tapped. In ViewController.swift I have Notification Observer and function which is executed every time observer catch notification that user tapped on button on watch ("notificationWatch" is global variable with notification key). Hope this will help to anybody.

Resources