SkillAgentSearch skills...

WatchConnectivityTutorial

A tutorial for building a simple WatchConnectivity app

Install / Use

/learn @ThumbWorks/WatchConnectivityTutorial
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Using WatchConnectivity

Abstract

One of the most anticipated announcements of WWDC 2015 was the exposure of an SDK to allow 3rd party developers the opportunity to put real software on the newly released Apple Watch. Before WWDC 2015, developers were allowed to put a UI on the watch, with the actual controllers of the app running on the iPhone itself. While this is decent for some cases, clear disadvantages appeared nearly immediately. Apps took time to load because the iPhone app had to wake up, do some things, then send content and state to the watch, at which point the watch could actually display something. An informal poll shows that folks generally didn’t use the apps that they’d installed on their watch because they felt too cumbersome.

At the keynote, Tim Cook and co discussed watchOS2. A true native experience for the Apple Watch. One where the actual code, not just the UI was running on the watch device itself. The possibilities are endless.

Introducing WatchConnectivity

In this post, I’ll be digging into a very specific enhancement that came along with watchOS2 which piqued my interest, called WatchConnectivity.

WatchConnectivity describes the communication API between the Apple Watch and the iPhone. The interface is straightforward, as there is a 1 to 1 relationship between devices. As in, there is 1 iPhone paired with 1 Watch. To communicate with your paired device, you simply access the WCSession.defaultSession() object, activate it, ensure that you’re connected, then send data. There are several ways to send data to the paired counterpart. I’ll only go into depth on one, specifically through - sendMessage:replyHandler:errorHandler: The rest of this post will describe how to set that up.

Our Catalyst App

The toy app that we’re going to create is a single view app on both the watch and the iPhone. They each contain 4 buttons: Top Left, Top Right, Bottom Left, Bottom Right. Tapping on 1 button, say, Top Left on the watch, should send a message to the counterpart where the results will be nicely displayed.

Starting the project

So let’s start by creating a new project which will be an iOS App with WatchKit App.

Notice the 3 different groups that are created when we create this type of app. This helps us draw a clear line between the iOS app, the watch extension where our code lives and the watch views.

Adding UI to the Watch

Moving on, let’s start adding some UI. Open the Interface.Storyboard. This is the storyboard that describes all of our watch UI. We’re going to be focusing on the main part of the interface, not the glances or notifications. So let’s add 2 groups that each take up the full width of the screen and half of the height of the screen. We’ll then place 2 buttons into each of these groups. This should give us a quadrant looking main watch view in our storyboard.

Our final UI element is a Label which describes which button was last pressed on the iPhone simulator. When a message comes from the iPhone, we'll want to update the label to display that latest activity from the phone.

Now that we’ve got the buttons and our activity label added, we’ll need to make IBOutlets so we have a reference to the UI elements, then we’ll need to make IBActions for the buttons so that we can actually do something when we press them.

We’re going to want to kick off our network communications in these IBActions. Since we’ll more or less be doing the same action 4 times, let’s make a nice convenience method that each of these will call. To keep things simple each IBAction sends an Int type representing it’s position in the grid. So Top Left is 0, Top Right is 1, Bottom Left is 2, Bottom right is 3. Mine looks something like this:

    func buttonPressed(offset : Int) {
        print("Button pressed \(offset)")
    }
    
    @IBAction func topLeftButtonTapped() {
        buttonPressed(0)
    }
    
    @IBAction func topRightButtonTapped() {
        buttonPressed(1)
    }
    
    @IBAction func bottomLeftButtonTapped() {
        buttonPressed(2)
    }
    
    @IBAction func bottomRightButtonTapped() {
        buttonPressed(3)
    }

Now that we’ve got the shell of this all figured out and we’re passing an appropriate simple identifier to our convenience method, we can generate the dictionary that we’d like to send over. Again for simplicity sake, we’re going to send over a dictionary in the form {“buttonTapped” : <offset of button pressed>}. Very simple code, we’re creating a dictionary.

The Send API

Now that we’ve got the data we’d like to send over, let’s take a look at the docs for - sendMessage:replyHandler:errorHandler:.

The message is obviously the content we’re going to be sending over. The replyHandler closure will get called if you’re counterpart determines that they want to actually reply to what you’re saying. In an app where the watch asks questions of the iPhone, this would be how the i iPhone responds. This is optional. The error handler takes care of scenarios where either device becomes disconnected or possibly other catastrophes. For the sake of brevity, we'll not be doing anything of any significance in the reply or the error closures. We will simply send our message.

    func buttonPressed(offset : Int) {
        
        // verify that this device supports WCSession (iPod and iPad do not as of β1
        if(WCSession.isSupported()) {
            
            // create a message dictionary to send
            let message = ["buttonOffset" : offset]
            
            session.sendMessage(message, replyHandler: { (content:[String : AnyObject]) -> Void in
                print("Our counterpart sent something back. This is optional")
                }, errorHandler: {  (error ) -> Void in
                    print("We got an error from our paired device : " + error.domain)
            })
        }
    }

So far this should be pretty straight forward. Assuming our WCSession is activated (it isn't yet) and a companion device is paired (none is), we should be able to send messages as easy as this. Again, we're not really doing much with the replyHandler, other than printing out that we got a reply.

Setting up the session

I'd mentioned a second ago that we need to do some WCSession boilerplate. This is pretty lightweight boilerplate, but it will mean the difference between this working and not. So we'll do 3 things:

  1. Keep a reference to the default WCSession object if this device supports WatchConnectivity (iPods/iPads do not as of β1).
  2. Set the delegate for our session and activate it. This starts us up and listens for requests from the other side through the WCSessionDelegate protocol. We'll need to declare that both the iPhone and the watch view controllers conform to the WCSessionDelegate protocol. We'll actually implement the protocol in a later section.
  3. Check our paired and connected state before we send our messages.

So first create a class level let session : WCSession!. Then override init() like this:

    override init() {
        if(WCSession.isSupported()) {
            session =  WCSession.defaultSession()
        } else {
            session = nil
        }
    }

Then override willActivate:

    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()

        if(WCSession.isSupported()) {
            session.delegate = self
            session.activateSession()
        }
        self.tappedLabel.setText("")
    }

So far our code should look [something like this] (https://github.com/ThumbWorks/WatchConnectivityTutorial/blob/watch-side-networking-implemented/WatchConnectivityTutorial/WatchConnectivityTutorial%20WatchKit%20Extension/InterfaceController.swift).

Implementing the iPhone side

So let's take a look at the iPhone here. Like last time we'll set up our UI first. Let's open up Main.storyboard and add some more buttons that we can interact with. Since you're using size classes (you ARE using size classes correct?), we'll need to set some constraints on the buttons since we always want them to be visible. Let's set up 4 buttons that are constrained to the outer edges of the view. There are probably several ways of adding constraints in Interface Builder, but I like to use the ole' Ctrl+drag from the button to the edge. The details of how you constrain these are not important but it is likely safe to have the buttons 10 pixels from their closest edges.

Next we'll add the IBOutlet to the UIViewController. We should be able to Ctrl+drag from the "New Referencing Outlet Collection" in the connections inspector on the right side to the code in the ViewController.swift just below the start of the @class InterfaceController. Be sure to do this 1 at a time starting at the top left button and moving clockwise. We'll do this so we can determine the offset in the collection of each button. For clarification sake, we'll be adding each button to the SAME referencing outlet collec

Related Skills

View on GitHub
GitHub Stars11
CategoryDevelopment
Updated1y ago
Forks2

Languages

Swift

Security Score

60/100

Audited on Jan 21, 2025

No findings