Check out our Bootcamp Schedule View Schedule

WatchKit 2 Hardware Bits: Using the Digital Crown

Steve Sparks

When WatchKit was announced, I was so excited to get the code and begin using the hardware on the Apple Watch. But like most everyone else, I was disappointed when WatchKit 1 leaned so heavily on the phone that hardware access was simply impossible. It felt a bit like the iPhone’s launch, when we asked about apps and were given Mobile Safari.

That changed when WatchKit 2 was announced. The big change is allowing code to run on the watch itself, rather than in the extension space on the phone.
Running on the watch increases the difficulty of some things like communication with your containing app, and it can lead to power consumption issues, but it unlocks so much potential that it’s worth it.

And most of that potential is in the hardware. The first and easiest piece to chew off is the Digital Crown.

In WatchKit 1, all we had was the WKInterfaceTable, which could be scrolled with the crown. With the new APIs, you can access the crown via a new interface class called a WKInterfacePicker. To use this class, you add it to your interface and add an outlet to your interface controller. In willActivate, you attach some items, call [self.picker focusForCrownInput] and you’re off.

Picker Types

The picker, like iOS pickers, requires a set of data items from which to pick. Unlike iOS, they are not retrieved via delegation, but set all at once via -setItems:. Why? Well, because the UI and the code are still running in two different processes. Changes to UI elements are batched up and transmitted in the next spin of the run loop.

The picker’s items are of type WKPickerItem. These items have a title, an accessory image, a caption and a content image. There are three methods of presentation: List, Stack and Sequence. Depending on the choice of presentation, you will set some of these values, but never all.

List

The list presentation is pretty close to the iOS representation of a UIPickerView. Things fade at the top and bottom of the list, and the selected item appears in the center. It’s good for the same sorts of things that you’d use a UIPicker for, such as a predetermined list of options (your favorite Pallookaville soda flavors, perhaps).

Filling your picker is pretty simple:

    NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:101];
    for(int ctr=0; ctr <= 100; ctr++) {
        WKPickerItem *item = [[WKPickerItem alloc] init];
        NSString *title = [NSString stringWithFormat:@"Title %d", ctr];
        item.title = title;
        [items addObject:item];
    }
    return [items copy];

The resulting list looks like this:

List presenation

I found that the list presentation works great with text, but not so much for images. The contentImage or accessoryImage will be scaled down to the row height.

Another thing I noted was that if you set a title and an accessory image, they become left justified. Titles without the accessory image are centered.

Stack

The stack offers a little animation similar to a Rolodex. What’s a Rolodex, you young whippersnappers ask?

This is a Rolodex

Apple’s version isn’t quite like this, but it’s similar. When it settles, only the selected card is visible.

For this, let’s switch the code to return image-based picker items. I used the Radial Chart Image Generator to create a ring of images and attached them to my picker with this code:

    NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:101];
    NSLog(@"requested items");
    // -images returns NSArray<WKImage>
    for(int ctr = 0; ctr <= 100; ctr++) {
        NSString *imgName = [NSString stringWithFormat:@"round%d",ctr];
        WKImage *image = [WKImage imageWithImageName:imgName];
        WKPickerItem *item = [[WKPickerItem alloc] init];
        item.caption = imgName;
        item.contentImage = image;
        [items addObject:item];
    }
    [self.picker setItems:[items copy]];

In Interface Builder, I also turned on “Focus: Outline with Caption.” The result was this:

Stack animation

Note: Not all images work well with the stack picker. Images that have transparency will show the backing image through those transparent holes.

Sequence

The sequence view is what you choose if you wish to spin through a set of images in a flat manner.
This is the one that is appropriate for most custom controls, as you can implement pretty much anything your heart desires. (I may put together a WKInterfaceSlider replacement that uses the crown. That needs to be a thing.)

Sequence view

Getting Events

The picker will fire off an IBAction when its value changes, so it’s easy to act upon it just as any other UI event. The picker supplies the selected index, which you can then map back to a meaningful construct.

Bonus: Use the Taptic Engine

Let’s make the watch tap the wrist as the value changes! This is simple enough to do…

- (IBAction)pickerChanged:(NSInteger)value {
    [[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeClick];
}

Now you will get tactile feedback as you scroll through your controller. There are a bunch of different haptic types; I’ll cover those in the next post.

Deployment Woes

It took me around 10 minutes to write the code necessary to work the Digital Crown, but deploying to the device was a long, sordid affair. To wit:

  • Download Xcode 7 beta, iOS 9 beta and watchOS 2 beta.
  • Install Xcode 7. This part is easy, just time consuming. We must download, copy and verify our standard multiple-gigabyte image. 4.22GB this time!
  • That said, the iOS 9 installation was pretty painless. A reasonably slim download at 2GB, and a nice new font!
  • The watch updates take a pretty long time, during which it must be connected to the charger and you’re required to enter the passcode a couple times.
  • Once the update was complete, the watch had turned off my “unlock when phone unlocks” setting, which was irritating.
  • Deploying the app to the watch is a bit of a hinky process. It may fail if you attempt to deploy a new copy while it is busily copying one up. I found myself following this process repeatedly:
    • Via the Watch app on the phone, find my test app and deselect “Show app on Apple Watch.”
    • Watch the Watch’s app launcher until the app icon disappears.
    • Via Xcode, launch the app again.
    • Eventually, Xcode gives up and says, “Finished running [xx] on Apple Watch.” If you look at the apps screen, it is slowly installing. (Images are big.)
    • When the icon finally fills in, you can launch your app and play with it. No log output, however. Use the Simulator if you want such nice things.
  • Battery life suffers tremendously for App Deployment. From 100% battery, using it for more or less continuous development for three hours left me at 35%.

Not once did I manage to get Xcode to show me the debug session on the extension. I wasn’t able to get console output, making NSLog useless. I am not sure how long it will take Apple to fix this, or whether I should just add my own mechanism for the purpose of remote logging.

In any event, that’s a topic for another day. In the meantime, we’ve got watch apps to write. Happy coding!

Similar Posts:

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project