Brian Gilham

Engineering leader, husband, and father

watchkit

Luck Favours the Prepared

Many of the best moments in my career have happened because of luck. Shortly after the Apple Watch was announced, Apple approached one of our clients about building a WatchKit app. It would be one of the first available on the App Store. Day one. They asked if we could help. Since I’d been doing a bit of work on the platform I, along with one of our client’s designers, flew down to Cupertino and spent a week working on the app at Apple’s campus.

Many of the best moments in my career have happened because of luck.

Shortly after the Apple Watch was announced, Apple approached one of our clients about building a WatchKit app. It would be one of the first available on the App Store. Day one. They asked if we could help.

Since I’d been doing a bit of work on the platform I, along with one of our client’s designers, flew down to Cupertino and spent a week working on the app at Apple’s campus. It was the highlight of my career. Here I was, working on a device that hadn’t even been released to the public yet. I was so lucky.

Or was I?

The truth is, I’d spent *weeks* poring over the WatchKit documentation, building apps, blogging about the SDK, and mocking up interfaces. I even 3D-printed a model of the watch – just to get an idea of the size. I was putting in the work. 

Did I know it would lead to such an amazing opportunity down the line? Hell no. I thought the Watch sounded interesting and I wanted to build apps for it. But when the chance to do something cool presented itself, I was ready.

We never know where our careers will take us. It’s important we find time to explore new things. Try out a new platform. Build a side project. Collaborate with someone new. You never know which exploration will yield something that changes your path forever.

Steve Jobs once said, “You can’t connect the dots looking forward; you can only connect them looking backwards.”

Which dots will you create today?

Until next time,

–Brian


How to get text input from the user

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2) One of the most interesting features of WatchKit — collecting text input through dictation — is also one of the least talked about. Let’s get started. Talk to me Dictation is handled using a standard modal, presented by calling presentTextInputControllerWithSuggestions:allowedInputMode:completion:on your currently-active WKInterfaceController. The controller runs asynchronously, waiting for the user to tap “Done” and confirm their input. Once that happens, the provided completion block is executed on the main thread and you can use the supplied results however you like.

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2)

One of the most interesting features of WatchKit — collecting text input through dictation — is also one of the least talked about. Let’s get started.

Talk to me

Dictation is handled using a standard modal, presented by calling presentTextInputControllerWithSuggestions:allowedInputMode:completion:on your currently-active WKInterfaceController.

The controller runs asynchronously, waiting for the user to tap “Done” and confirm their input. Once that happens, the provided completion block is executed on the main thread and you can use the supplied results however you like.

You should always call presentTextInputControllerWithSuggestions:allowedInputMode:completion: on your WatchKit extension’s main thread.

Suggestions

When presenting the dictation controller, you can optionally provide an array of NSString objects representing suggested input for the user. In a messaging app, for example, you might want to suggest generic responses a user could send to their friend.

Input Modes

You have to choose which of the three WKTextInputMode options you would like to present to the user.

  • WKTextInputModePlain: Dictation/suggested text only. No emoji. The emoji button will be hidden.
  • WKTextInputModeAllowEmoji: Dictation, suggested text, and non-animated emoji.
  • WKTextInputModeAllowAnimatedEmoji: Dictation, suggested text, and both animated and non-animated emoji.

If you skip providing suggestions and set the input mode to WKTextInputModePlain, the user will be sent directly to the dictation interface.

Completion

The completion block is passed an NSArray containing the input from the user. If the modal is dismissed before any input is given, this array will be nil. You can usually expect the array to contain a single NSString object representing the text input.

However, it can also contain an NSData object representing an emoji image if you’ve allowed the user to provide one. You can use that NSData object to create a UIImage, or use it directly in a WKInterfaceImage.

Since the text input interface is presented modally, the presenting WKInterfaceController will be re-activated before the completion block is called.

Simulator

It’s worth mentioning a few things when it comes to testing dictation in the simulator:

  • While you can specify any input mode you like, the simulator only supports providing text input using a suggested string. You can’t speak into your computer’s mic and have it converted to text. Similarly, the simulator does not support selecting either type of emoji.
  • The dictation screen will appear blank. It’s important to know that dictation will begin immediately when used on an actual Watch. The user will have to tap a “Done” button to confirm the dictated text before it is passed back to your app.

How to debug an iOS app while the associated WatchKit app is running

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2) Since the introduction of openParentApplication:reply: I’ve seen developers struggle to debug their iOS app while running a WatchKit app in the simulator. If you haven’t worked with extensions before, the solution may not be obvious. Here’s how to pull it off: Build & run your iOS app in the simulator. Wait for it to finish launching, then hit the stop button in Xcode.

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2)

Since the introduction of openParentApplication:reply: I’ve seen developers struggle to debug their iOS app while running a WatchKit app in the simulator. If you haven’t worked with extensions before, the solution may not be obvious. Here’s how to pull it off:

  1. Build & run your iOS app in the simulator.
  2. Wait for it to finish launching, then hit the stop button in Xcode.
  3. Switch your active target to your WatchKit app, build, and run it.
  4. When the Watch app has finished launching, tap your iOS app’s icon in the main simulator window.
  5. In Xcode’s menu bar select Debug > Attach to Process.
  6. Select your iOS app from the list. Chances are you’ll find it under Likely Targets.

If you take a look in the Debug Navigator (⌘ + 6), you’ll notice the debugger is now attached to both your iOS app and the WatchKit extension. You can click on each target in the navigator window to select which console output you’d like to view. If Xcode encounters a breakpoint it will automatically switch to the correct target.

Keep in mind that you’ll have to repeat this process each time you re-run your WatchKit app.


One weird trick to “fix” openParentApplication:reply:

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2) A quick perusal of the Apple Developer Forums shows more than a few developers have experienced strange bugs when calling openParentApplication:reply: in a WKInterfaceController. Most often, it seems, when making multiple requests in quick succession. In an app I worked on recently we were hitting all sorts of strange behaviour. Sometimes openParentApplication:reply: would mysteriously be called twice. Every once in a while the reply block would fail to ever be called — despite being certain the delegate was functioning correctly and completing work in a background task.

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2)

A quick perusal of the Apple Developer Forums shows more than a few developers have experienced strange bugs when calling openParentApplication:reply: in a WKInterfaceController. Most often, it seems, when making multiple requests in quick succession.

In an app I worked on recently we were hitting all sorts of strange behaviour. Sometimes openParentApplication:reply: would mysteriously be called twice. Every once in a while the reply block would fail to ever be called — despite being certain the delegate was functioning correctly and completing work in a background task.

Frustrating, to say the least.

However, I’ve discovered a solution that has made communication with the iPhone rock-solid. Essentially you have to begin — and end, after two seconds — an empty background task right at the beginning of the delegate method. It should be the absolute first thing you do. Afterward, kick off a background task for the real work.

Here’s an example:

The theory is the bogus background task prevents the OS from killing your app immediately. I honestly couldn’t tell you for sure.

All I know is it’s made openParentApplication:reply: far more reliable.


How to round the corners of a WKInterfaceImage

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2) The Human Interface Guidelines recommend using black as the background colour across your entire app. Why? It allows the background to blend in with the bezel surrounding the display, giving the illusion there isn’t a bezel at all. In my experience, rounding the corners of your full-width images can help this illusion. A quick look at the WKInterfaceImage class reference, however, might make you think this isn’t possible.

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2)

The Human Interface Guidelines recommend using black as the background colour across your entire app. Why? It allows the background to blend in with the bezel surrounding the display, giving the illusion there isn’t a bezel at all.

In my experience, rounding the corners of your full-width images can help this illusion. A quick look at the WKInterfaceImage class reference, however, might make you think this isn’t possible. Where’s the setCornerRadius: property?

It isn’t possible. Not directly, anyway.

Poking around, you’ll notice that WKInterfaceGroup does have a setCornerRadius: method. Place your WKInterfaceImage inside a group, set the corner radius, and you’ll notice it clips the corners of your image beautifully.


How to Display an Image Using WKInterfaceImage

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2) If you take a look at the class documentation for WKInterfaceImage, you’ll notice there are three different methods for displaying an image. For many developers new to WatchKit this has proven to be a bit confusing. When should you use one over the other? Where are the images coming from? Here’s a quick guide. setImage: This method sends a UIImage — or multiple, if you’re creating an animation — from your WatchKit extension to your WatchKit app.

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2)

If you take a look at the class documentation for WKInterfaceImage, you’ll notice there are three different methods for displaying an image. For many developers new to WatchKit this has proven to be a bit confusing. When should you use one over the other? Where are the images coming from? Here’s a quick guide.

setImage:

This method sends a UIImage — or multiple, if you’re creating an animation — from your WatchKit extension to your WatchKit app.

setImageData:

This method sends an NSData object containing one or more images from your WatchKit extension to your WatchKit app. This saves the overhead of creating a UIImage if you are trying to set an image with data loaded directly from a file or downloaded from the Internet.

If you have to send an image from the phone to the Watch, this is the most efficient way to do so.

setImageNamed:

This method displays an image that has already been stored in the WatchKit app’s bundle, or cached on the device. If you are trying to displaying an image from the bundle, be sure to specify the full name and the file extension.

Caching

If you are displaying an image using setImage: or setImageData: that you’ll show more than once, cache it using the addCachedImage:name: method on WKInterfaceDevice. Afterward, you can use the specified cache name when calling setImageNamed:.

Make sure to clear images out of the cache as needed — you get 20mb of storage and it isn’t pruned automatically. To remove an image from the cache, you can use the WKInterfaceDevice method removeCachedImageWithName:. To clear all cached images use removeAllCachedImages.

Storyboards

One thing to keep in mind: while Interface Builder will allow you to specify an image from anywhere in your project, it will only be displayed if it is included in your Watch app bundle.