Creating a simple Weather App for Apple Watch. Part 2: Watch connectivity

Mathias Lidal 
11. nov. 2016

In part 1 of this tutorial I showed how to add Apple Watch support to an IOS app and how to create the user interface. We ended up with a functional Apple Watch app but with hardcoded data. You can either complete this tutorial or clone the repository from github and checkout the branch tutorial_part1. Now I'll show how to get actual data from the webservices and present them to the user. This involves using the WatchConnectivity framework to get data from the IOS App.

Setup WatchConnectivity session

The first step is to setup and start the watchConnectivity session. This needs to be done on both the watch and the phone, and should happen as soon as possible. On the phone a good place to execute this is in AppDelegate didFinishLaunchingWithOptions, on the watch it should be in awakeWithContext in the initial Interface Controller.

WatchConnectivity on phone

Let's start with the phone side. To setup the connection you need the following code:

if WCSession.isSupported() {
let session = WCSession.sharedSession()
session.delegate = self

First we check that WatchConnectivity is supported (this might not be the case, e.g. if you're running the app on an Ipad) If connectivity is supported, retrieve the singleton session, set the delegate class and call activateSession(). This assumes that self implements the protocol WCSessionDelegate. After calling activateSession, your delegate will receive a callback to the function session(session: WCSession, activationDidCompleteWithState activationState: WCSessionActivationState, error: NSError?) If activationState is .Activated, your watch is connected and you can start transferring data. It makes sense to wrap this code in a singleton object to avoid having to keep references to the WCSession all over the place. Therefore, start by creating a new file WatchSessionManager.swift, in the group Services, and add the following code:

import UIKit
import CoreData
import WatchConnectivity

class WatchSessionManager : NSObject, WCSessionDelegate {

static let sharedManager = WatchSessionManager()

private override init() {

private let session = WCSession.defaultSession()

func startSession() {
if WCSession.isSupported() {
session.delegate = self

func session(session: WCSession, activationDidCompleteWithState activationState: WCSessionActivationState, error: NSError?) {

In AppDelegate.swift add the following:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let manager = WatchSessionManager.sharedManager
return true

This will create and start the session. Any other class which needs to communicate with the watch can now access the shared WatchSessionManager object. WatchConnectivity provides several different methods to communicate between phone and watch. These come in two main groups:

  • Asynchronous background transfer
  • Synchronous messages

When using background transfer one side sends a package of data to the system, which then forwards it to the other side at some point in the future. We'll use this to transfer the current location and weather. The function we'll be using is

updateApplicationContext(applicationContext: [String : AnyObject])

This transfers an application context to the watch. The context is a dictionary containing property list types. This means that to send arbitrary objects they must be conterted into valid property list types. Add the following code to WatchConnectivityManager.swift

func updateCurrentWeather(currentWeather : Weather) {
let report = currentWeather.reports[0]

var weather = [String:String]()
weather["placename"] =
weather["symbol"] = report.symbol.variable
weather["temperature"] = String(format: "%.1f °", report.temperature)
weather["precipitation"] = String(format: "%.1f mm", report.precipitation)

try! session.updateApplicationContext(["CurrentWeather": weather])

This function gets an object of class Weather, retrieves the relevant bits of information (place, weather symbol, temperature and precipitation) and sends it as a dictionary This needs to be used when the app has retrieved user location and weather, which happens in LocationsViewController.swift. First, we need at get the shared WatchSessionManager object. Add the following line, right above the declaration of fetchedResultsController (line 30)

var watchConnectivityManager = WatchSessionManager.sharedManager

This gives us the manager to work with. Then, on line 244, above the call to self.tableView.reloadData(), add the following line:


WatchConnectivity on watch

To receive the weather information we need to setup connectivity on the watch side as well, we do this in InterfaceController.swift. First import the framework. Add the following line at the top:

import WatchConnectivity

Then we need to setup the session. In awakeWithContext, add the following lines:

let session = WCSession.defaultSession()
session.delegate = self

Note that we don't need to call WCSession.isSupported() as WatchConnectivity is always supported on the watch. You also need to make InterfaceController implement  WCSessionDelegate. Change the class definition to the following:

class InterfaceController: WKInterfaceController, WCSessionDelegate {

Once the application context is transferred from the phone, it is available on the watch as the property receivedApplicationContext on the WCSession object. We need to send this to the WeatherController to display. As we set up a segue from InterfaceController to WeatherController in part1, we can use the method contextForSegueWithIdentifier(segueIdentifier: String) to transfer the weather information. Add the following code to InterfaceController.swift

override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
if segueIdentifier == "Show Weather" {
if session.receivedApplicationContext["CurrentWeather"] != nil {
let weather = session.receivedApplicationContext["CurrentWeather"] as! [String:String]
return weather
return nil

Here we first check that the segueIdentifier is the correct one. If so, and if the context has been transferred, get the dictionary containing weather information and send is as the context for WeatherController Next, we need to present this information in WeatherController. Add the following lines to awakeWithContext

// Configure interface objects here.
if context != nil && context!.count != 0 {
let dict = context as! [String:String]
weatherSymbol.setImage(UIImage(named: dict["symbol"]! + ".png"))

If the context is not nil and contains keys, we update the view outlets with the correct values. Finally, in order to see the weather symbol, we need to make the Assets.xcassets catalog from the iphone app available on the watch. Select the Assets catalog and ass watch Extesnion to the Target Membership. To try this out, run the watch target, start the app on your phone and wait until it has found current weather. Then you should be able to see the same weather on your watch.

Show saved locations

The final step in this tutorial is to show the list of saved locations. For this we'll use a different function in WatchConnectivity, we'll send a message to the iphone app requesting the list of saved locations. The function call we use is

public func sendMessage(message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)?, errorHandler: ((NSError) -> Void)?)

This sends a message in the form of a dictionary to the counterpart, and receives the reponse in a handler closure. First we need to remove the dummy code we entered in part 1. Replace the declaration of locations with the following line:

var locations = [[String:String]]()

Replace the awakeWithContext in LocationsController.swift with the following

override func awakeWithContext(context: AnyObject?) {

// Configure interface objects here.
let session = WCSession.defaultSession()
session.sendMessage(["Command": "SavedLocations"], replyHandler: {
reply in
self.locations = reply["Locations"] as! [[String:String]]
self.locationsTable.setNumberOfRows(self.locations.count, withRowType: "LocationRow")
for (index, location) in self.locations.enumerate() {
let controller = self.locationsTable.rowControllerAtIndex(index) as! LocationRow["placename"])
}, errorHandler: nil)

Here we get the WatchConnectivity session and send a message containing a simple key-value pair (needed to identify the message on the phone app). In the replyHandler we get an array of weather dictionaries (similar to the one used in WeatherController) wrapped in a dictionary. The last step is to add the code on the iphone side to generate this reply. Here we use the function

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)

This receives a message, prepares the reply and executes the replyHandler. Add the following code to WatchSessionManager.swift

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], 
replyHandler: ([String : AnyObject]) -> Void) {
if message["Command"] as? String == "SavedLocations" {
let weather = getSavedLocationsWeather()

private func getSavedLocationsWeather() -> [[String:String]] {
let locations = locationService.getSavedLocations()
var watchWeather = [[String:String]]()
let group = dispatch_group_create()
for location in locations {
weatherService.getWeather(location, completion: {
weather in
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
return watchWeather
  1. We check that the message is requesting saved locations.
  2. We get the weather data all saved locations and execute the replyHandler with the weather data wrapped in a dictionary (as this is the expected parameter for the handler)
  3. Getting the actual data is extracted into a separate function. First we get the list of saved locations
  4. Iterate over the locations and get the current weather for each location. We use dispatch_group_enter/dispatch_group_leave to record when each call to getWeather finishes (as it is an asynchronous call)
  5. Extract the relevant parts of each weather object into a dictionary and save in the watchWeather array
  6. Wait for all calls to getWeather to finish and return the result.

didReceiveMessage will by default execute in a background thread, therefore we can block in getSavedLocationsWeather without fear of affecting the UI on the iPhone. After this is done you can build and run the project and any locations you have saved on the iphone app should be available on your watch.

Next steps

To see the source code for the final project, checkout the branch tutorial_part2. In this tutorial I have shown an example on how to create a simple watch app, create the user interface and communicate with the iPhone app. If you're interested in learning more I recommend you look at the apple documentation, and the sessions from WWDC 2015 and 2016.