A guide for watchOS 3, because the watch app programming guide is not obvious.
Companion App
The companion app is the iOS app, aka the parent or host app.
- You CANNOT open companion app programmatically
- You can communicate with the companion app, and at best put the app in background state
Setup bi-directional session
First, setup sessions between watch app and companion app using WCSession
.
You need to setup for both directions.
On iOS’s app delegate, or watchOS’ extension delegate, setup the session like this:
func setupWatchSession() {
if WCSession.isSupported() {
let session = WCSession.default()
session.delegate = self
session.activate()
}
}
The delegate manages state change, and also receiving of messages:
extension AppDelegate: WCSessionDelegate {
public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
public func sessionDidBecomeInactive(_ session: WCSession) {
}
public func sessionDidDeactivate(_ session: WCSession) {
}
public func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
}
}
The first 3 delegate methods are mandatory, even if empty.
The 4th method session:didReceiveMessage:
is for receiving messages from the other side. It is optional method, but we use it in next section.
Ways to Communicate
There are a number of ways, and you choose what is right for your app.
I will mention what is easiest – sendMessage
. Let’s look at the signature:
func sendMessage(_ message: [String : Any], replyHandler: (([String : Any]) -> Void)?, errorHandler: ((Error) -> Void)? = nil)
- You send a message payload in the form of a dictionary with
sendMessage
- The other side recieve via didReceiveMessage (one of
WKSessionDelegate
method) - The other side can reply via the
replyHandler
- You get back the reply in your reply handler
This is a simple send and reply mechanism, triggered by one side.
Open Companion App in Active State
This is a very common scenario – watch app wants to open companion (iOS) app.
That is NOT possible.
The best we developers can manage is this:
- Watch app communicates via
sendMessage
- iOS app receives message
- iOS app reply it’s current state – active or not – using
UIApplication.shared.applicationState
- Watch app receives reply, and if state is not active, inform the user that he must open the iOS app
The above can be slightly improved. When iOS app is opened, proactively sendMessage
to watch app on it’s status.
Debug BOTH watch and iOS targets
It seems like Xcode cannot run and debug watchOS and iOS targets at the same time.
But it is actually possible, but not obvious.
The steps:
- Run watch app
- Xcode > Debug > Attach to process > select your iOS app
If you want to debug your iOS app before it is being launched, you can “Attach to Process by PID or Name…”
Pitfall: Xcode Build Errors
I encountered Xcode bug where the watch app build settings does not include the correct build architecture.
Not sure why, but Xcode always screw up with the build architecture.
Plus, Xcode throws confusing errors such as WatchKit App doesn't contain any WatchKit Extensions. Verify that the value of NSExtensionPointIdentifier in your WatchKit Extension's Info.plist is set to com.apple.watchkit
and Embedded Binary Validation utility Error
, but really, the root cause is that it didn’t build the watch extension.
Make sure under Watch Extension target > Build Settings > Valid Architectures, it includes armv7k
(for device) and i386
(for simulator).
Technical notes could be helpful.