WatchConnectivity: Say Hello to WCSession
Check out previous blog posts on WatchOS 2 if you haven’t already:
WCSession is the magic ingredient of WatchConnectivity. So let’s dive in!
WCSession.defaultSession() will return the WCSession singleton for transfering data between your iOS and Watch app. But of course there are several caveats to keep in mind when using WCSession!
The first one is that you have to set a delegate for the session and activate it!
“The default session is used to communicate between two counterpart apps (i.e. iOS app and its native WatchKit extension). The session provides methods for sending, receiving, and tracking state.
On start up an app should set a delegate on the default session and call activate. This will allow the system to populate the state properties and deliver any outstanding background transfers.” – Apple Documentation Notes
So your code will look something like this…
1 2 3 |
let session = WCSession.defaultSession() session.delegate = self session.activateSession() |
At this point, I would recommend wrapping your WCSession Singleton into your own Singleton, which you can use throughout your app:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import WatchConnectivity // Note that the WCSessionDelegate must be an NSObject // So no, you cannot use the nice Swift struct here! class WatchSessionManager: NSObject, WCSessionDelegate { // Instantiate the Singleton static let sharedManager = WatchSessionManager() private override init() { super.init() } // Keep a reference for the session, // which will be used later for sending / receiving data private let session = WCSession.defaultSession() // Activate Session // This needs to be called to activate the session before first use! func startSession() { session.delegate = self session.activateSession() } } |
So now you can activate your session from application:didFinishLaunchingWithOptions in the AppDelegate and use it everywhere else in your app:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { // truncated... func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Set up and activate your session early here! WatchSessionManager.sharedManager.startSession() return true } // truncated... } |
But activating the session is not enough of course. WCSession
has multiple checks you need to go through so that your application is not doing the extra work of formatting your data for transfer.
isSupported
Check if session is supported on this iOS device. Session is always available on WatchOS
If you have a Universal app, for example, WCSession
will not be supported on the iPad (since the Apple Watch does not pair with the iPad). So always make sure to do the isSupported()
check in your iOS project:
1 2 3 4 5 |
if WCSession.isSupported() { let session = WCSession.defaultSession() session.delegate = self session.activateSession() } |
This means the WatchSessionManager
Singleton in your iOS app will need to adjust to deal with the possibility of the WCSession
not being supported (hello optionals!):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Modification to the WatchSessionManager in the iOS app only class WatchSessionManager: NSObject, WCSessionDelegate { // truncated ... see above section // the session is now an optional, since it might not be supported private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil // starting a session has to now deal with it being an optional func startSession() { session?.delegate = self session?.activateSession() } } |
iOS App State For Watch
If you’re sending data from your iOS app to the Watch, you need to do a few extra checks so you’re not wasting CPU power for processing your data for transfer when the Watch is not in a state where it can receive the data.
paired
This is pretty self-explanatory. In order to transfer data from the iOS device to the Watch, the user must own an Apple Watch and have it paired to your iOS device.
watchAppInstalled
A user might have a paired device, but of course they can choose to delete your Watch App from their device. So you have to check that your Watch App is actually installed on their paired Apple Watch in order to do the data transfer.
If the user gets to a point in your app where you believe they will benefit from the Apple Watch version of your app, that would be a good place to do this check and prompt the user with a value proposition to install your watch app if they have a paired device without your app on it.
To make these checks much easier to do as you keep working with the session in your singleton and possibly throughout your application, I like to create a validSession
variable in my iOS app:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// Modification to the WatchSessionManager in the iOS app only class WatchSessionManager: NSObject, WCSessionDelegate { // truncated... see above private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil // Add a validSession variable to check that the Watch is paired // and the Watch App installed to prevent extra computation // if these conditions are not met. // This is a computed property, since the user can pair their device and / or // install your app while using your iOS app, so this can become valid private 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 let session = session where session.paired && session.watchAppInstalled { return session } return nil } // truncated... see above } |
complicationEnabled
Finally, if you have a complication for your app, you have to check if the complication is enabled. I won’t go into much detail on Complications in my WatchConnectivity tutorials, but if you’d like to learn more, watch the super useful and comprehensive WWDC 2015 Creating Complications with ClockKit session.
sessionWatchStateDidChange
Note that there’s a delegate method to notify you of when any of the above WCSession
states change in case you need that information in your iOS app:
1 2 3 4 |
/** Called when any of the Watch state properties change */ func sessionWatchStateDidChange(session: WCSession) { // handle state change here } |
So, for example, if your app needs the Watch App to be installed, you can implement this delegate method to monitor that your Watch App was in fact installed and let the user follow through on the additional flow in your iOS app to finalize setup.
reachable
In order to use Interactive Messaging to send data between the iOS and Watch actively running apps right away, you need to do an additional check to make sure that the two apps are in reachable state:
Reachability in the Watch app requires the paired iOS device to have been unlocked at least once after reboot. This property can be used to determine if the iOS device needs to be unlocked. If the reachable property is set to NO it may be because the iOS device has rebooted and needs to be unlocked. If this is the case, the Watch can show a prompt to the user suggesting they unlock their paired iOS device.
I like to add an extra valideReachableSession variable to my singleton for using with Interactive Messaging:
1 2 3 4 5 6 7 8 9 10 11 12 |
// MARK: Interactive Messaging extension WatchSessionManager { // Live messaging! App has to be reachable private var validReachableSession: WCSession? { // check for validSession on iOS only (see above) // in your Watch App, you can just do an if session.reachable check if let session = validSession where session.reachable { return session } return nil } |
If the session is not reachable, you can prompt the user to unlock their iOS device as suggested by Apple. To know that the user has unlocked their device, implement the sessionReachabilityDidChange delegate method:
1 2 3 4 5 6 7 8 |
func sessionReachabilityDidChange(session: WCSession) { // handle session reachability change if session.reachable { // great! continue on with Interactive Messaging } else { // 😥 prompt the user to unlock their iOS device } } |
That’s it! You now should understand all the little gotchas with WCSession
, so we can now move on to the fun part – actually using it to send and receive data between the Watch and iOS app!
You can view the full WatchSessionManager Singleton on Github here.