Help with first watch app

I’m new to IOS development, and have spent the last month learning the development environment and the framework. I was able to successfully create a phone app with Xcode 13 that communicates with an autopilot I designed and built for my boat. It communicates with a BLE peripheral designed into the autopilot. All well, and good. The phone app includes a map and and allows for placing custom annotations on the map marking the location of fish I catch, or shallow spots on the lake.

I decided it would be handy to have a watch app that would contain a few controls such as Lock onto the current heading, and turn left and right 10 degrees. Those functions are already implemented on the phone app, so I though it would be a simple task to send a message to the phone to activate those functions.

For the life of me I can’t figure out how to send messages between the two apps.

I backed up and created a simple phone app with a companion watch app. Same problem, no clue how to make them talk.

I’ve searched online, but all the tutorials are way more complicated than what I need, and I can’t ferret the functionality I’m looking for from any of them.

Can someone point me in the right direction to get started? Or if it’s not too involved, explain how to setup the link between the two apps.

Accepted Reply

There are 4 methods for phone/watch communication under Watch Connectivity, each related to the amount/type of data and the required immediacy of transfer. Now that I better understand what you're doing, my perception is that your use-case needs the sendMessage method, which you seem to have done, rather than transferUserInfo (which is simpler to implement and more reliable, but less immediate).

However there are some potential issues with sendMessage from the watch to the phone if the phone is not reachable or not active, as reported here (as recently as 3 months ago). Advances in iOS over the last year or two have improved the ability for background processing (in essence, when the app is not active on screen) - but I haven't had cause to delve into it and so can't offer advice. I suggest, if you haven't already done so, that you test your watch/phone/autopilot communication with your phone (phone app) in various states - to ensure that the watch's commands to the autopilot are reliably implemented.

Best wishes and regards, Michaela

Replies

I helped with a similar question a few months ago in this post https://developer.apple.com/forums/thread/689484?answerId=686982022#686982022

If your iPhone app is in SwiftUI, the sample code I provided should form a workable basis for your use case. Note that the phone app and watch app both use a Data Model (i.e. standalone class, not a SwiftUI View) for communicating with each other and processing the data. The models also provide and collect data from their respective SwiftUI Views, i.e the user presses a button or enters data on the device in a SwiftUI view, which then gets collected by the Data Model and processed and/or transmitted to the other device.

In my sample code (above) the user wanted to transmit count data from the watch: in your case you (presumably) want to transmit a command to the phone. You can do this by having a common struct e.g named Command, in each Data Model, that describes the command that you're passing. Then in the transferUserInfo (i.e. send) and didReceiveUserInfo (receive) functions you use an instance of Command to send/receive your navigation instructions. Having received the Command from the watch, your iPhone Data Model would then send the appropriate instructions to the BLE autopilot.

My original post is a very simplified version of watch connectivity and there are circumstances where things can get more complicated. I therefore advise that, at some point, you read the documentation link I provided earlier.

I hope that this helps. Regards, Michaela

PS - it's also possible for the watch to communicate directly with your BLE navigation device, using the same code as on the iPhone (except for the UI, given the watch's small screen): but maybe that's another question/answer for another day.

Second PS - depending on how the iPhone app and device are configured, the phone app might not on-send the command to the autopilot immediately - which could be problematic!!! My suggestion therefore is that your watch app communicates directly with the autopilot, as per the phone app.

  • I'm getting closer, but still have an issue...

    Thanks for the reply! I was starting down that road with a ViewModel shared between the phone and watch app. The ViewModel implements the WCSessionDelegate I added debug code verifying that the sessions activate with complete code of 2, on both devices (in the simulator). I was getting excited, but then I ran into the following issue that's got me stumped again. I thought it might have been a simulator issue, so I targeted my phone and watch but my message never gets to the watch.

    2022-03-05 21:49:15.473640-0800 WatchPlay[8680:107318] [WC] -[WCSession onqueue_handleUpdateSessionState:]_block_invoke dropping as pairingIDs no longer match. pairingID (null), client pairingID: (null)

    Goggle provided no insight into what's going on here. I looked at all the project settings, and don't see any reference to pairingID.

  • Mmm, I haven't had that message before, but I'm aware that it's been a problem for others from time to time. I suggest checking if your session (instance of WCSesssion) returns .ispaired == true on the phone immediately after your session gets activated. If it returns false, then see if the iPhone and Watch really are paired. It might also be an idea to check all other aspects of the paired device information as per the documentation https://developer.apple.com/documentation/watchconnectivity/wcsession. What versions of Xcode, iOS and watchOS are you using? If needs be, I can try to replicate the problem here. Regards, Michaela

Add a Comment

Well, I've got one way communication now, but only when I run on the actual devices. running from the simulator I still get the pairingID issue.

I can send from the watch to the phone, but the other direction, I have the following issue when updating my watch View from the ViewModel...

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

I'm getting closer, but time to pack it in for the night.

  • In your Watch's session didReceiveUserInfo function you need to update the ViewModel variable (i.e. data that get displayed) using DispatchQueue.main.async { update the var } as you probably did in the phone app if using my sample code. This is because communication sessions are run on a background queue, whereas views (and their associated data) can only be updated on the main queue.

    Good luck and regards, Michaela

  • Sorry, I didn't read the complete thread of the previous post you linked above until just now. I'm so new to this, I'm not aware of what it means to create an app using swiftui, but I'll look into that when I get back on this.

    I had followed the strategy of another tutorial whereby you share the view model and app classes defined in the phone app with the watch app. I'll create another combined project, and look for differences in the way the app class (main) is implemented in the watch.

    if nothing jumps out at me, I'll try setting up my project the way you recommended now that found your suggested implementation.

    BTW, I'm a retired EE getting back into coding to as I build various imbedded projects. iOS development has been an interesting experience, as my background is imbedded HW controller design, and C/C++. Swift is somewhat strange and interesting at the same time.

    thank you so much for taking time to respond. I'll let you know how this project gets along.

Add a Comment

I just added "DispatchQueue.main.async { self.rcvMsg = msgStr }" where recvMsg is my published variable, and I now have two way communication working! Thanks again for your time.

I integrated the approach gleaned from the test app into my autopilot app, and I can now control the autopilot from my watch. The watch sends messages to the Bluetooth manager on the phone, and those messages are forwarded to the Bluetooth peripheral in the autopilot. Works like a champ!

  • Well done! It's good to see another retireee doing app development. I'd already assumed you are very tech savvy, although, yes, Swift and particularly SwiftUI can be hard to get one's head around (was for me, anyway) after years of procedural programming. Sharing of the phone and watch ViewModal code is the best way to go (though potentially more complex), especially if both devices will have the same functionality. Normally I use a separate Framework (library) for all of my Data Model (ViewModel) code, with OS specific code as needs be. This is because most of my apps now are Multiplatform (Mac, iPad, iPhone, Watch and sometimes TV), with data sharing via the Cloud: my watch apps are now standalone, i.e. not tethered to the phone. However, your need appears to be for reliable realtime communication, which probably wouldn't suit a Cloud approach

Add a Comment

There are 4 methods for phone/watch communication under Watch Connectivity, each related to the amount/type of data and the required immediacy of transfer. Now that I better understand what you're doing, my perception is that your use-case needs the sendMessage method, which you seem to have done, rather than transferUserInfo (which is simpler to implement and more reliable, but less immediate).

However there are some potential issues with sendMessage from the watch to the phone if the phone is not reachable or not active, as reported here (as recently as 3 months ago). Advances in iOS over the last year or two have improved the ability for background processing (in essence, when the app is not active on screen) - but I haven't had cause to delve into it and so can't offer advice. I suggest, if you haven't already done so, that you test your watch/phone/autopilot communication with your phone (phone app) in various states - to ensure that the watch's commands to the autopilot are reliably implemented.

Best wishes and regards, Michaela

I was surprised to find that the phone app continued to pass my commands through to the autopilot even after locking it. I would have been perfectly happy to have kept it on while using the autopilot, so the functionality to continue while locked was an unexpected bonus.

The HW autopilot’s only function is to maintain a heading. Anything else will be features I add in the phone app. I plan on implementing a route tracking feature once I verify the HW autopilot is doing its job. That and a few others would be tedious if not impossible to implement on the watch, so my plan is to have the phone be the brains of the operation.

  • Ooo, that's good! Also useful knowledge for me, in case I need realtime communication in the future. I've done a lot of work with route tracking (e.g. running (as in exercise) app, so would be happy to help out if needs be.

  • Since you offered, one thing I've been looking into without much success is how to add an annotation to a map with a tap or long press gesture. Most of the resources don't cover doing it with SwiftUI.

    i considered dropping a pin at the current location, which I do know how to do, then moving it, but haven't been able to work that out either.

    I'm starting to think I have to use a coordinator but that looks messy, and didn't want to jump down that rabbit hole if I didn't need to.

    Any help would be appreciated.

  • I came up with an approach to add pins (fixes) at a location on the map that doesn't require dragging... I created a view and added it to the map Zstack. That view has a button that allows for adding an annotation at the current region center which I mark with an X. I just pan and zoom the map so that the X is where I want the fix and hit the button. Worked like a champ, and allows for exact placement of the fix