how-to-apple-watch-thumb

Apple is coming out with a smartwatch! You’re probably thinking, “duh.” (If you’re not, stop reading this tutorial and go to your favorite tech site.) Ideally, you’ve also already had the heads-up on tips developers need to know about the huge launch.

Now, let’s have fun and start building for Apple Watch.

In this tutorial, we’re going to show how to implement a simple WatchKit app to get you up and running with Apple’s latest technology. We’ll discuss other use cases in following posts (like glance and notification views), but for now, we just want to lay the groundwork for a wide range of Apple Watch development.

Project Setup

We’ll be following the code found in our WatchKitDemo project, which you can get here – with the full project hosted on our Github project page. This project lays the foundation on the iPhone side and we’ll be adding a WatchKit target and code in this tutorial. While we won’t go over every single line, we will hit the key topics for WatchKit. Don’t worry though, you’ll find the rest of the code nicely commented (as code always is… right?).

Adding WatchKit

First, you need to add a Watch App target to your iOS app.

Note: When we say ‘iOS’, we mean iPhone/iPad/etc. When we say ‘Watch’, well, we mean the Apple Watch :)

To do this, select your project in the explorer and click the + button at the bottom of the project/targets pane


Fig 1.1

Then navigate to the Apple Watch group.

Select WatchKit App and hit Next.


Fig 1.2

Verify that your settings look similar to (if not just like) the screenshot below – and ensure that both checkboxes are checked. We’ll be making both a notification and a glance scene in the next tutorial using this code base. Select Finish when your settings are right.


Fig 1.3

You’ll notice there are two new folders in your project directory: WatchKitDemo WatchKit Extension and WatchKitDemo WatchKit App.


Fig 1.4

The WatchKit Extension will contain the logic and the core functionality for your Watch app. The ‘WatchKit App’ only contains resource files. Select the Interface.storyboard to see the interface builders for each of our watch use cases.

Now, let’s take a quick peek at how this will look in the simulator. From your scheme manager, select WatchKitDemo WatchKit App > iPhone 6 and build & run.


Fig 1.5

Do you only see a phone simulator? No worries! In the simulator, go to Hardware > External Displays > Apple Watch 38mm. There it is! Not very exciting yet, but we’re getting there.

Now, run the actual iOS app (Schemes > Watchkit Demo > iPhone 6).

It crashed, didn’t it? That’s because this demo depends on Apteligent – and we added an NSAssert to make sure it’s initialized properly. Check out your AppDelegate.m and insert your Apteligent app ID (it’s free to get here) to get it working properly. We’ll use this tool to make sure everything is working as planned!

Once you’ve added your app ID, build and run once more. You should see a screen that looks like this:


Fig 1.6

This simple app uses the GPS on the iPhone to track your location. It’s worth noting that the iPhone is where the heavy lifting is done for your Watch app – which is why we’re starting here on the iOS side.

Click the Start Tracking Location button and allow the app to grab your location.

If nothing happened except a log in the console that reads ‘Error retrieving location,’ that is OK.  Your simulator just isn’t simulating a location. Easy fix:

In the simulator, go to Debug > Location > City Drive. This will simulate a nice drive around the Bay Area. Some numbers should appear and start to change values as the location updates.


Fig 1.7

Now that we have the base of our project up and running, let’s start building the Watch app!

Building the Watch App

We’re going to break this tutorial into three posts that correlate to each component of a Watch app: Building the watch app itself, building a glance, then wrapping up with the notification.

Keep in mind you don’t have to necessarily build all three for a Watch app – you can mix and match according to your desired user experience.

Creating the Interface

Open the Interface file in Xcode. Notice the Main screen at the top.

This is the interface for our Watch app, and we’ll get started there. We’re going to make a simple interface that shows the map of where the user is and their speed.

First, drag a map interface object into the main screen as shown:


Fig 2.1

You’ll notice it snapped into place under the status bar, filling the width of the device.

Next, drag a label underneath the map.


Fig 2.2

Again, you’ll notice how the label snapped into place. This is one of the big differences in Apple Watch. Views are stacked vertically by default. You can create more custom views called groups that allow you to create horizontal layouts as well. For the sake of simplicity we’re staying vertical so the views we add will simply stack on top of one another.

Now let’s change the dimensions to make sure the screen is filled.

Select the Label in the interface and change the Position – Vertical attribute to Bottom. You’ll notice the label snaps to the bottom of the screen.


Fig 2.3

Next, select the map and set the Size – Height to Size to Fit Content. Now the map and label will fill the entire display to maximize visibility.


Fig 2.4

Next, let’s make connections between the code and the UI.

In your Assistant Editor (View > Assistant Editor > Show Assistant Editor), open the WatchKit Extension > InterfaceController.h file in the right hand pane, and your Interface.storyboard in the left.

Control-click on your map and drag into the InterfaceController.h to create a new outlet named mapView as shown:


Fig 2.5

Now, do the same thing with the label – and name it speedLabel.

Notice that the classes of these objects are different than what you’re used to on iOS. We have WKInterfaceMap and WKInterfaceLabel. That’s because they have different properties than their UIKit counterparts – so the properties and methods you’re used to may not translate 1-1 between the iPhone and the Watch!

Communicating with the iPhone

Now that we have our UI ready to go, it’s time to pull some data into it.

With the iPhone’s GPS module pulling the users location, speed and other information, it’s up to the Watch to pull that info from the iPhone – and the APIs make that super easy to do.

We’re going to leverage a single method call that sends a request from the Watch to the iPhone. The call basically says, “Hey! We need something!”

Use the static method belonging to WKInterfaceController:

+ (BOOL) openParentApplication:(NSDictionary *)userInfo
                         reply:(void (^)(NSDictionary *replyInfo, NSError *error))reply

This method sends a request to the parent app on the iPhone with an NSDictionary full of custom user info.

Now in our app, add the following method at the top of your WatchKit Extension’s InterfaceController.m file:

- (void) requestLocationFromPhone
{
    [WKInterfaceController openParentApplication:@{@"request":@"location"}
                                           reply:^(NSDictionary *replyInfo, NSError *error) {

       // the request was successful
       if(error == nil) {

           // get the serialized location object
           NSDictionary *location = replyInfo[@"location"];

           // pull out the speed (it's an NSNumber)
           NSNumber *speed = location[@"speed"];

           // and convert it to a string for our label
           NSString *speedString = [NSString stringWithFormat:@"Speed: %g", speed.doubleValue];

           // update our label with the newest location's speed
           [_speedLabel setText:speedString];

           // next, get the lat/lon
           NSNumber *latitude = location[@"latitude"];
           NSNumber *longitude = location[@"longitude"];

           // and update our map
           MKCoordinateSpan span = MKCoordinateSpanMake(0.05, 0.05);
           CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude.doubleValue, longitude.doubleValue);

           // drop a pin where the user is currently
           [_mapView addAnnotation:coordinate withPinColor:WKInterfaceMapPinColorRed];

           // and give it a region to display
           MKCoordinateRegion region = MKCoordinateRegionMake(coordinate, span);
           [_mapView setRegion:region];
       }

       // the request failed
       else {
           [Apteligent logError:error];
       }
   }];
}

Hopefully the comments shed some light onto what’s happening in detail, but here’s the gist: the extension is requesting the current location from the iPhone. Upon receiving a response, it parses the serialized location data and displays the information on the Watch display.

Notice that if an error is found, we log it in Apteligent so we can easily figure out the problem.

We just created a standalone method called requestLocationFromPhone. Now we need to call it – so place the following code in the InterfaceController.m awakeWithContext: method as shown:

- (void)awakeWithContext:(id)context {
    [super awakeWithContext:context];

    // Configure interface objects here.
    [self requestLocationFromPhone];
}

This will request our location as soon as the watch app is woken up, which is perfect for our first test.

The First Test

In order to run the Watch Extension, you’ll want to set the scheme to WatchKitDemo WatchKit App before launching. This will switch from running the iPhone app to running your Watch Extension!

Go ahead and build and run under that scheme and you’ll likely notice a compile error stating that the Apteligent library could not be found. That’s because we need to explicitly add it to the Watch extension target. It’s simple to do – but worth noting that the Watch extension and iOS app have different sets of bundled libraries. Third party SDKs need to be explicitly added to both.

To do this, simply go to the Build Phases tab in the WatchKitDemo WatchKit Extension target and drag the Apteligent library into Link Binary With Libraries. You’ll also want to add the SystemConfiguration.framework to the build phases as well.


Fig 2.6

To finish the setup, make sure to import the Apteligent header at the top of your WatchKit Extension’s InterfaceController.m file like this:

#import "Apteligent.h"

Now build and run again… voila! It works! Kinda… where’s our map data?


Fig 2.7

We set up a method on the Watch to receive the data, but nothing on the iPhone yet to send the location.

Once again, it’s a simple method call in your phone’s AppDelegate:

- (void) application:(UIApplication *)application
handleWatchKitExtensionRequest:(NSDictionary *)userInfo
               reply:(void (^)(NSDictionary *))reply
{
    ViewController *mainController = (ViewController*)  self.window.rootViewController;

    NSDictionary *serializedLocation = [mainController getSerializedLocation];

    reply(@{@"location": serializedLocation});
}

This method parses the request that we send from the WatchKit Extension InterfaceController and does something synchronously. Once it’s done, it calls the reply() block – which sends a custom NSDictionary back to the watch. Pretty slick.

We specifically said this performs something synchronously so our reply() block doesn’t go out of scope during a long-running task like a network request. Be careful of this to ensure your reply is consistently sent back to the Watch!

A Few More Things to Finish Up

Now in our app, we’re going to get the latest location from our view controller, serialize the location data and shoot it back to our Watch.

Import the view controller into your AppDelegate.m file:

#import “ViewController.h”

We need to be able to extract the serialized location from the view controller, so insert the following into ViewController.h in your iPhone app:

- (NSDictionary*) getSerializedLocation;

Now implement the method as shown in ViewController.m:

- (NSDictionary*) getSerializedLocation
{
    // get the latest location from our GPS
    CLLocation *location = _locationManager.location;

    // extract the coordinate
    CLLocationCoordinate2D coordinate = location.coordinate;

    // return a serialized dictionary with the current location attributes
    return @{
             @"latitude":   [NSNumber numberWithDouble:coordinate.latitude],
             @"longitude":  [NSNumber numberWithDouble:coordinate.longitude],
             @"altitude":   [NSNumber numberWithDouble:location.altitude],
             @"speed":      [NSNumber numberWithDouble:location.speed]
             };
}

Again, the comments should speak for themselves. In short, we are getting the latest location from our GPS manager, serializing the data and preparing it to be sent back to the Watch.

Now, make sure the WatchKitDemo WatchKit App target is selected, and build and run!


Fig 3.1

You should see a pin plopped down somewhere on a California highway. If so, congratulations – you have a fully-functioning Apple Watch app! If not, check your simulator and make sure it’s still simulating an interesting location (Debug > Location > Freeway Drive is a good one for this demo).

Final Touches

Our app is a little boring on the Watch right now, since we only received one static location and speed. With a simple timer, we can ping the iPhone more frequently to update the location as we travel.

At the top of our InterfaceController.m add a new ivar called _updateTimer as shown:

@interface InterfaceController() {
    NSTimer *_updateTimer;
}

@end

Next, in our willActivate method, add this timer initialization:

- (void)willActivate {
    // This method is called when watch view controller is about to be visible to user
    [super willActivate];

    _updateTimer = [NSTimer scheduledTimerWithTimeInterval:10.f
                                                    target:self
                                                  selector:@selector(requestLocationFromPhone)
                                                  userInfo:nil
                                                   repeats:TRUE];
}

This will create a timer when the app is visible to the user. It will fire every 10 seconds, calling the requestLocationFromPhone method and updating the location.

Of course, we want to stop updating when the view disappears as well, so implement the didDeactivate method:

- (void)didDeactivate {
    // This method is called when watch view controller is no longer visible
    [super didDeactivate];

    [_updateTimer invalidate];
}

You now have an auto-updating app – build and run to see the result!

Closing Notes

The pattern shown here – the iPhone managing all the data for a lightweight dictionary transfer to the Watch which is then used as a secondary display – can be replicated across many different use cases. Regardless of how you use it, this is a great example of how to transfer data from the iPhone to the Watch to enhance your app.

However, there’s more to Apple Watch apps than just the app itself. There are also glances and notifications, which we’ll cover in our next post. It will pick up right where this tutorial left off, so stay tuned!

If you’d like to download the completed code from this tutorial, you can do so here – with the full project hosted on our Github project page.

Getting Ready for Launch

With new APIs and devices, there are bound to be issues, errors, and exceptions in your app once the Watch is launched to the public. By using Apteligent, you can monitor your app’s performance and keep your app smooth and your users happy.

We implemented a few lines for error checking in this tutorial, but there’s more to be done!

Happy coding, everyone, and feel free to ping us if you have any questions @apteligent!