“My Biggest WatchKit Mistake”

To celebrate the arrival of the Apple Watch, we asked a few of our friends
to share the most useful thing they learned building for Apple’s new platform.

You can read part 2 here.


Natasha Murashev

iOS Engineer at Capital One and blogger at Natasha The Robot

My biggest mistake was thinking that you could open your iOS app by sending a message from the Watch. WatchKit has an API for sending a quick message to your iOS app, which triggers necessary background processes; just call WKInterfaceController class function when you need to activate it:

// WKInterfaceController
class func openParentApplication(userInfo: [NSObject: AnyObject],
                                    reply: (([NSObject: AnyObject]!, NSError!) -> Void)?) -> Bool

// launches containing iOS application on the phone
// userInfo must be non-nil

However, this will not open the iOS application for the user! In WatchKit beta it opened the iOS application in the simulator, so, I thought that was part of its function. In reality, it only opens the iOS app in the background, enough to pass information and quickly perform vital tasks.

Watch Natasha’s video: Architecting Your App for the Apple Watch


Brian Gilham

Senior Mobile Developer at TWG and blogger at Five Minute WatchKit

The biggest mistake when working solely in the iOS simulator is that it’s easy to forget that much of your app’s functionality relies on a BTLE connection that can, well, sometimes suck. The simulator attempts to address this by adding some latency to your requests but response times on-device can sometimes be quite a bit slower.

As you move through the design and development of your app, ask yourself three questions:

  1. What happens if this view or action takes a long time to respond?
  2. How will the app react if it doesn’t get a response at all?
  3. What can I do to mitigate those problems?

The solutions to these issues will differ from app to app, but here’s a few things to consider:

  1. If there’s even the slightest chance you’ll use an API response or image more than once, cache it as aggressively as possible. If a news feed doesn’t update more than once every 15 minutes, don’t waste time hitting the API every app launch.
  2. Take advantage of opportunities to download data before the user needs it. Implementing background fetch in your iOS app, combined with a strategy for caching/persisting data, can make a huge difference.
  3. If you have to make the user wait, consider taking the time to display a delightful loading animation. Your app should never make the user feel like it’s frozen. Similarly, make sure to update the UI and let the user know if a request just flat out failed.

Good luck, and I can’t wait to see what you create!


Conrad Kramer

Creator of Workflow

Early on we ran into issues with our image picker. It would take forever to load, and the memory usage would crash the WatchKit extension. After much trial and error, here are my top tips for sending an image to the Watch:

  • Make a thumbnail of the image to fit the exact size that it will be displayed on screen.
  • Compress the image into JPEG or PNG data
  • Call -[WKInterfaceImage setImageData:] with the data — Using data instead of UIImage objects is faster and can be more memory efficient.

If you need to use animated images, you can use +[UIImage animatedImageWithImages:duration:]. Then, encode it to data with NSKeyedArchiver, and send that.

* Note that the Watch does not respect the duration property, and uses the duration provided in -[WKInterfaceImage startAnimatingWithImagesInRange:duration:repeatCount:].


James Robert

CTO at Media Predict

My number one mistake was trying to reuse graphical elements from the iPhone app on the Watch. You have to rethink your design from scratch to really optimize your approach for such a constrained environment.

You have to be very careful about using images - As Conrad mentions, you can get performance to a decent level using jpeg-compressed NSData, but I just replaced my images with a group and set a background color/border radius like you do in CSS (in this case, the image was a checkbox). The screen is seriously tiny, so you need to do anything you can to give the text more space. I replaced my square checkbox, dot in the center, with this. If you want to see more, check out the fully functioning app.


Curtis Herbert

Independent Developer & Designer

The biggest mistake I made when working on my WatchKit app for Slopes was not being intelligent about updating my views. It’s easy to think “I can just update the screen as often as I want”, like we do on iOS, regardless of whether the values have actually changed. But remember! All this has to happen over Bluetooth, which has limited bandwidth.

If you pay attention to the console logs as you run your app, you’ll see that WatchKit tries to drop what it thinks are duplicate updates to a view. Also, any updates you make after didDeactivate is called are likely to be ignored. Apple is very aggressive with keeping updates sent over Bluetooth minimal, but hasn’t provided many tools on WKInterfaceController or WKView to track this kind of state for us (such as getters on these properties, omitted for obvious reasons).

I implemented a common caching controller subclass that all my controllers inherit from to keep track of key view values (such as a label’s text, an image’s name, or a view’s hidden flag). It’s a little primitive at the moment, the result of only a half day’s work in the WatchKit labs, but feel free to check it out on GitHub. It simplified my controllers a ton, abstracting away a lot of state tracking which was essential as I moved to more event-driven code through MMWormhole and WFNotificationCenter.

Once I accepted I’d need to track view state on my own, and architected around that, a lot of the odd issues I saw went away.

You can watch Curtis’s WatchKit Communication presentation or read his blog.


Neil Kimmett

Developer at M&S Digital Labs

The biggest mistake we made with our WatchKit app was including lots of padding around text elements. When designing for desktop and for mobile, we’re used to nice big margins between the edges of our screens and any text written on those screens. However, in WatchKit, if you use a black background, the frame of the watch acts as a natural margin for your content. So butt that text right up against the edge of the screen! It’ll look strange in the simulator, but natural on the device. It has the added benefit of giving you a bit more screen real estate to play with — a very limited resource on the Watch!

You can read Neil’s Blog


Kristina Thai

iOS software engineer at Intuit

One of the biggest mistakes I made was trying to pass custom class objects in the reply dictionary in handleWatchKitExtensionRequest as a way to send information back to my WatchKit extension. While the custom object goes into the replyInfo dictionary just fine, it can’t be serialized by the plist file and will throw an error. If you do send a custom object, you’ll end up seeing an error message that looks like this:

Error Domain=com.apple.watchkit.errors Code=2 "The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]" UserInfo=0x61000006f640 {NSLocalizedDescription=The UIApplicationDelegate in the iPhone App never called reply() in -[UIApplicationDelegate application:handleWatchKitExtensionRequest:reply:]}

Since the custom object can’t be serialized by the plist file, it thinks that the parent iPhone app never called reply() in handleWatchKitExpensionRequest even if it did.

There’s a few different ways to share data between the iPhone app and the WatchKit extension:

  1. You can still use the reply dictionary to pass simple pieces of data. Turned out that all I really needed from my custom object was a couple of strings for the Watch display, and those were easily passed in the dictionary. The same goes for a status code or quick message (strings and other primitive types). You can also pass arrays and dictionaries within your reply dictionary for slightly more complex data. `
  2. If you do need all of the properties of your custom object, first set up App Groups and then use either NSUserDefaults or NSFileManager depending on your needs (note: when reading and writing to a shared container using NSFileManager, you’ll also have to protect against data corruption).

  3. An interesting cross of these previous two is to use NSKeyedArchiver to archive your custom object, then you can pass it via the reply dictionary. See the following example:

On the phone side build your reply dictionary

NSMutableDictionary *reply = [NSMutableDictionary new];
MyCustomObject *myObject = <something you need to send>;
reply[@"myKey"] = [NSKeyedArchiver archivedDataWithRootObject: myObject];
NSAttributedString *myString = <some attributed string>;
reply[@"otherKey"] = [NSKeyedArchiver archivedDataWithRootObject: myString];

And unpack it back on the watch side

NSData *objectData = replyInfo[@"myKey"];
MyCustomObject *myObject = [NSKeyedUnarchiver unarchiveObjectWithData: objectData];
NSData *stringData = replyInfo[@"otherKey"];
NSAttributedString *myString = [NSKeyedUnarchiver unarchiveObjectWithData: stringData];

Thanks to Brian Montz for this snippet!

The important lesson: take care to determine what information you need, and the best way to share this data between the WatchKit extension and the iPhone app.

You can read Kristina’s WatchKit Tutorials or tweet her @kristinathai


Orta Therox

Head of mobile at Artsy & Design Dictator at CocoaPods

Don’t be afraid to not ship. Odd as it sounds, sometimes your app being on another screen doesn’t quite work out as you imagine.

Given the lack of testing infrastructure for Watch apps (though I hear Cedar has a way), there can be a large amount of shipped code that does not have an explicit design contract in code. E.g. if another developer wants to make changes they have to know all the contexts it affects. I got to a point where I felt my Watch app was going to be too large of a surface for bugs in contrast to the trade off of cool new things. Just because you can doesn’t mean you should.

Thanks to everyone who submitted! You can read part 2 here.