Brian Gilham

Engineering leader, husband, and father

apple

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


Accessibility in WatchKit

Many developers aren’t aware of the accessibility options on the Apple Watch. In this quick talk, presented at this month’s tacow meetup, I talked about why accessibility is important and give a brief overview of VoiceOver, Dynamic Text, Grayscale, and Reduce Transparency/Motion. A few people asked about my slides, so I thought I’d share.

Many developers aren’t aware of the accessibility options on the Apple Watch. In this quick talk, presented at this month’s tacow meetup, I talked about why accessibility is important and give a brief overview of VoiceOver, Dynamic Text, Grayscale, and Reduce Transparency/Motion.

A few people asked about my slides, so I thought I’d share.


How to Disable App Transport Security

(Originally published at FiveMinuteWatchKit.com) 06/11/15: Steven Peterson has posted an update, showing how to explicitly define per-domain exceptions to ATS. The method described below should only be used as a last resort. If you eagerly fired up iOS 9 and watchOS 2 yesterday you may have noticed something strange, at least if your app relies on NSURLSession. In iOS 9, Apple is introducing App Transport Security. ATS enforces best practices during network calls, including the use of HTTPS.

(Originally published at FiveMinuteWatchKit.com)

06/11/15: Steven Peterson has posted an update, showing how to explicitly define per-domain exceptions to ATS. The method described below should only be used as a last resort.

If you eagerly fired up iOS 9 and watchOS 2 yesterday you may have noticed something strange, at least if your app relies on NSURLSession.

In iOS 9, Apple is introducing App Transport Security. ATS enforces best practices during network calls, including the use of HTTPS.

From the docs:

ATS prevents accidental disclosure, provides secure default behavior, and is easy to adopt. You should adopt ATS as soon as possible, regardless of whether you’re creating a new app or updating an existing one. If you’re developing a new app, you should use HTTPS exclusively. If you have an existing app, you should use HTTPS as much as you can right now, and create a plan for migrating the rest of your app as soon as possible.

If, however, your app relies on a non-HTTPS API outside of your control, this is problematic.

While the documentation also mentions:

App Transport Security (ATS) lets an app add a declaration to its Info.plist file that specifies the domains with which it needs secure communication.

It currently fails to describe the Info.plist keys needed to specify exceptions to ATS.

After a lot of searching last night, Steven Peterson found one solution: disabling ATS entirely. Simply include the following in your Info.plist file:

Obviously this is not ideal.

He also turned up two other keys: NSExceptionDomains and NSIncludesSubdomains. However, neither one of us has been able to figure out how to successfully use them.

With any luck the documentation will be updated in short order and we can update our apps to use ATS properly. Until then, Steven’s solution does just the trick.


Improve the Accessibility of Images in Your WatchKit App

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2) While browsing through the WatchKit Framework Reference recently, I noticed an interesting class: WKAccessibilityImageRegion. Intrigued — and unable to find anyone else who had tried it out — I put together a quick example. Essentially, WKAccessibilityImageRegion allows you to add accessibility labels to specific parts of an image. Let’s say you have a photo of a group of people. By default, WatchKit allows setting the usual accessibility properties for the image as whole: label, hint, value, and traits.

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

While browsing through the WatchKit Framework Reference recently, I noticed an interesting class: WKAccessibilityImageRegion. Intrigued — and unable to find anyone else who had tried it out — I put together a quick example.

Essentially, WKAccessibilityImageRegion allows you to add accessibility labels to specific parts of an image. Let’s say you have a photo of a group of people.

Tim Cook, Jony Ive, Dave Grohl, and Nathan Mendel

By default, WatchKit allows setting the usual accessibility properties for the image as whole: label, hint, value, and traits. By employing WKAccessibilityImageRegion, however, we can call out each person individually.

Let’s define an area around Tim Cook’s face. We simply create a new WKAccessibilityImageRegion object, set the frame and label, and add it to the group using setAccessibilityImageRegions:.

(Note: In this example, I’ve set the above photo as a background image on a group. While the documentation claims otherwise, I’ve been unable to get this working with a standalone WKInterfaceImage)

Voila! With one simple change, Voiceover not only calls out the image as a whole, but identifies Tim Cook within it.

Please excuse my hairy arm.

Whether you are working with static images, or generating images dynamically, WKAccessibilityImageRegion offers a simple way to improve the accessibility of the images in your WatchKit app.


Quick WKInterfaceImage Tips

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2) Now that users everywhere are firing up their brand-new Apple Watch, I’ve been extremely busy working on updating a few apps. But I wanted to take a moment and share two quick image-related tips with you. Rounded Corners I see one question being asked on Stack Overflow again and again. How the heck do I round the corners on a WKInterfaceImage?

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

Now that users everywhere are firing up their brand-new Apple Watch, I’ve been extremely busy working on updating a few apps. But I wanted to take a moment and share two quick image-related tips with you.

Rounded Corners

I see one question being asked on Stack Overflow again and again. How the heck do I round the corners on a WKInterfaceImage? It’s easy. Like many UI tricks in WatchKit, the secret is WKInterfaceGroup.

Simply nest your image inside a group sized to fit its content and set the cornerRadius on the group as desired. The group will clip your image, giving you the appearance of rounded corners on the image itself.

Why nest a WKInterfaceImage inside the group, instead of setting the group’s backgroundImage? That leads me to my second tip.

Image Placeholders

In many apps, developers opt to lazy-load their images. From a performance standpoint, this is a wise choice. Many apps, however, simply show a large gap in the content until the image loads. This can be confusing to users. At best, they guess something might load there eventually. At worst, your layout appears broken.

The solution, then, is to display some sort of placeholder for the image. In many of the apps I’ve worked on we accomplish this by nesting a WKInterfaceImage inside a WKInterfaceGroup and using the group’s backgroundImage to display a placeholder graphic or animation. Once the image inside the group is set, it completely covers the background image.

It’s an extremely simple technique, but it’s proven to be a huge help for users.


Submitting Your WatchKit App

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2) Now that submissions are open for WatchKit extensions, many developers are running into issues and bugs that didn’t present themselves when working in the simulator. This post is an attempt to aggregate tips and solutions to common problems. Of course, be sure to check out Apple’s official guidelines. Many of these tips are gleaned from posts in the dev forums and my own experience submitting a WatchKit app.

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

Now that submissions are open for WatchKit extensions, many developers are running into issues and bugs that didn’t present themselves when working in the simulator. This post is an attempt to aggregate tips and solutions to common problems. Of course, be sure to check out Apple’s official guidelines.

Many of these tips are gleaned from posts in the dev forums and my own experience submitting a WatchKit app. I’ll update the list as time goes on. Have something to add? Give me a shout.

iTunes Connect

  • The section for uploading your Apple Watch screenshots and app icon will only appear once you have uploaded your first WatchKit build.

Screenshots

  • Ensure your screenshots are 312x390px.
  • Screenshots should depict only your app’s interface — and use the entire space to do so.
  • Do not frame your screenshots in an Apple Watch “frame” or image.
  • Do not refer to your Apple Watch app in your iPhone app screenshots.
  • Do not add additional text/promo material to your Watch screenshots.
  • Despite the earlier requirement for screenshots to be taken only on device, you can now submit screenshots from the simulator. To do so, simply hit ⌘ + S or File > Save Screen Shot. The resulting images will be saved to your desktop, by default. Thanks to reader Jeff Rames for prompting this tip.

App Icons

  • Ensure your app icon does not include an alpha channel. This will result in an automatic rejection during validation and produces a cryptic error message for some.
  • If you encounter the error file names must match pattern “*@x.png”, ensure your Watch app icon is contained in an asset catalog included with the Watch app target. You cannot share an asset catalog between your iPhone app and your Watch app.
  • Make sure your icon does not contain a black background. This will cause it to blend into the background on the phone and has been a source of rejections for some.

Deployment Targets

  • While your iPhone app may support versions of iOS lower than 8.2, your WatchKit extension’s deployment target must be 8.2 or higher.
  • If you employ a framework in your WatchKit extension, your iPhone app’s deployment target must be 8.0 or higher. This is due to your WatchKit extension being bundled with your host app.

Build Process

  • If your build process happens outside of Xcode, or you have a custom build script, be sure your final zipped IPA follows the conventions described in this dev forums post.

Version & Build Numbers, Bundle Identifiers

  • Ensure the build and version number for your iPhone app, WatchKit extension, and Watch app are exactly the same.
  • Your WatchKit extension’s bundle identifier should use the bundle identifier for your iPhone app as a prefix. For example: if your iPhone app’s bundle identifier is com.company.AppName, your WatchKit extension’s bundle identifier should be something along the lines of com.company.AppName.watchkitextension.
  • David Olesch, iOS Lead at Jackrabbit Mobile, adds: “also make sure your app target and watch app target have the same display name. I got rejected because I forgot the other.”
  • An Tran adds: Ensure your WatchKit app does not include “WatchKit” or “Apple Watch” in the name.

Provisioning

  • Remember that your WatchKit extension requires its own app ID and provisioning profile.
  • From the ever-awesome Nick Arnott: If your WatchKit app fails to code sign with Xcode 6.3, you now need a profile for the WatchKit app in addition to the WatchKit extension.

App Store Description

  • If you reference the Apple Watch in your App Store description, be sure to follow Apple’s guidelines for capitalization and such. A few developers have faced rejections due to not following the guidelines.
  • As a quick reference, Apple Watch should always be written in English with an uppercase A and an uppercase W. It should not be UPPERCASE, lowercase, or use the Apple logo in place of the word “Apple”.

Performance

  • Ensure, as much as possible, that your app feels responsive in the simulator. If it feels at all sluggish in the simulator, it will be doubly so on device. At least one developer has been rejected for this.
  • If you are making use of openParentApplication:reply: I strongly suggest you follow the advice given in this post. In my testing with an actual Watch, openParentApplication:reply: was extremely unreliable without using the tips in that post. Another Watch labs participant has confirmed that to be the case for them as well. At least one developer has had their app rejected due to openParentApplication:reply: calls being killed on device before completion.

Swift

  • If you use Swift in your iPhone app, be sure to set the “Embedded Content Contains Swift” build setting to NO for your frameworks and extensions and YES for your iPhone app target.

App

  • Duplicating the functionality of a clock face, or displaying the time in a way that could be confused with one, will result in a rejection. Judging by the posts in the developer forums, this rule was supposed to appear in the HIG but was missed. It will be added soon.
  • Looking at this post in the dev forums, it appears your Watch app cannot exceed 50mb in size.

How to use Handoff in your WatchKit app

(Originally published at FiveMinuteWatchKit.com, before the release of watchOS 2) Considered by many to be an optional feature in their iOS/OSX apps, I believe Handoff will play an integral role in our WatchKit apps. Need the user to sign in before they can use your Watch app? Handoff. Want to present content a big too long for the Watch? Handoff. Need specific OS-level permissions for your Watch app? Asking the user to Handoff to this phone and pop open an interface specifically asking for/setting those permissions up.

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

Considered by many to be an optional feature in their iOS/OSX apps, I believe Handoff will play an integral role in our WatchKit apps.

Need the user to sign in before they can use your Watch app? Handoff. Want to present content a big too long for the Watch? Handoff. Need specific OS-level permissions for your Watch app? Asking the user to Handoff to this phone and pop open an interface specifically asking for/setting those permissions up. When I see developers complain about WatchKit’s limitations, 99% of the time I think their problems could be easily solved by handing off to the iPhone and presenting custom UI.

Let’s take a look at how to implement it.

Give me your hand

To broadcast your app’s current activity, simply call updateUserActivity:userInfo:webpageURL: any time during the execution of your WKInterfaceController’s code. You should not create an NSUserActivityobject, as you would on iOS or OSX.

The updateUserActivity parameter requires an NSString describing the type of activity you’d like to broadcast in reverse-DNS format, by convention. It cannot be nil or an empty string.

userInfo expects an NSDictionary, as you might expect. This is where you can send along any data or state information the iPhone might need to pick up what the user is doing on the Watch. The contents of userInfo must be one of the following types: NSArray, NSData, NSDate, NSDictionary, NSNull, NSNumber, NSSet, or NSString.

Finally, you can optionally specify webpageURL to let the receiving iPhone continue your activity in Safari. Any URL scheme other than http or https throws an exception.

Invalidation

An activity will be broadcasted until the OS decides to kill it, you call updateUserActivity:userInfo:webpageURL: with a new activity, or call invalidateUserActivity to manually invalidate it.

Glances & Notifications

It isn’t obvious as first, but you can also call updateUserActivity:userInfo:webpageURL: from your Glance and custom notification interfaces. If you specify an activity that is recognized by the phone, it will allow the user to continue it there. But you can also “continue” the activity in your WatchKit app.

When your app is launched from a Glance or custom notification interface, any activity broadcasted from those interfaces will be passed to your root interface controller in the handleUserActivity: method.

If your app uses a page-based interface, handleUserActivity: will be called for each controller that is part of the initial interface. You don’t need to register the activity type in your WatchKit app for this to work. When overriding this method, don’t call super.

Simulator

As usual, the iOS simulator does not support testing Handoff. Unless you have received an invite to one of the developer labs and can test on actual hardware, you’ll have to hope for the best or wait for the Watch to be released.


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.