WatchKit Development Tips

I’ve been working full-time on the Apple Watch component of my new WhereNotes app since mid-January. I was also fortunate to be invited to (and attend) an Apple Watch Lab in Cupertino. Over the past three-and-a-half months, I’ve assembled a lot of little tips and tricks, and I’ve included many of them in this post. I hope that something here helps you with your Apple Watch development.

  • You’re probably familiar with methods like applicationWillEnterForeground: and applicationDidEnterBackground: on UIApplication that are called just prior to their corresponding notifications (UIApplicationWillEnterForegroundNotification and
    UIApplicationDidEnterBackgroundNotification, respectively). The equivalent (and little-known) WatchKit NSExtensionContext notifications are:
NSExtensionHostWillEnterForegroundNotification
NSExtensionHostDidEnterBackgroundNotification
NSExtensionHostWillResignActiveNotification
NSExtensionHostDidBecomeActiveNotification
  • My own personal experience along with the experience of others in the Apple Developer Forums indicates that you’ll have a much better and more reliable debugging experience by testing the Watch while it’s on the charger.
  • Unlike iOS where you can update interface elements (almost) whenever you’d like, with WatchKit, you can only update elements on the currently active/visible interface controller. Updates can safely be made until didDeactivate is called (note also that you cannot update interface elements within this method). This means that if you plan to update a currently-hidden interface controller (for example, if you’re viewing a modal controller “on top”), you’ll need to perform the update in the presenting controller’s willActivate method, which is called when the modal controller is dismissed.
  • In addition to assets that are included in your WatchKit app bundle, each app is allowed a 5MB image cache that can be populated and managed by your extension using methods on WKInterfaceDevice. Sending images from your extension to the Watch takes time and battery, so if you plan to re-use an image (even once), it’s worth caching. If you send an image using addCachedImage:name:, that image is automatically PNG-encoded and sent to the cache, regardless of whether or not PNG is the best format (it’s the safest format). If your image can be represented as a JPG, I’d highly recommend using addCachedImageWithData:name: instead. Encode the image as a JPG, and experiment with the quality setting. Not only will the image transfer go much more quickly, but you’ll save a lot of space in the cache allowing you to store more images.
  • Related to the prior advice, note that you are allowed to cache images on a background thread (according to an Apple employee on the Developer Forums). I use this technique in my own Watch app to pre-cache images just before they’re needed.
  • If you use the aforementioned image cache, there is no built-in method to determine the oldest-used image to evict. If your app is managing many images, you’ll probably want to wrap your own manager around the cache.
  • To test notifications on your Apple Watch, turn off Wrist Detection in the General settings of the Apple Watch Companion app.
  • To force-quit your app, hold down the side button, then hold it down again (note that force-quitting your app does not force quit your extension).
  • Improve loading time by minimizing the amount of work you do in willActivate.
  • Consider what happens if a user launches your Watch app before your iPhone app and design accordingly. App review will catch this if you don’t.
  • Remember that your Watch app is running as an extension. As such, your Watch app has much tighter memory constraints than your iPhone app. If you’re processing large images, for example, it’s better to offload that work to your iPhone app (using openParentApplication:reply:). Also note that the simulator doesn’t implement these memory constraints, so you must test on actual hardware.
  • To find out if your app is paired to a Watch, set a BOOL from your Watch app in a shared NSUserDefaults (using a shared app group) that your iPhone app can also access.
  • To synchronize data between the iPhone and Watch, you can either call into your iPhone app to perform all data updates (using openParentApplication:reply:), or use Darwin notifications to send events between your extension and your iPhone app. Darwin notifications don’t support a data payload, so if you want to pass data with your notifications, take a look at the very useful MMWormhole project.
  • While you can update and refresh interface elements with timers (and in willActivate, of course), you can also use KVO if your data source supports it. This is how I do it in my Watch app. Using this method, elements are only updated when they change, saving both communication overhead and battery.
  • If you need to track interface controllers, consider passing a reference to self in awakeWithContext: to establish a relationship. I have used this pattern extensively in my own app with my JBInterfaceController subclass. Using techniques like this allows you to do things like setup delegate patterns and think of your controllers a little more like UIViewController.
  • Unless your scenario requires it, think carefully about whether you need to have “live updates” that immediately sync between the Watch and the iPhone. Users won’t typically use both devices at the same time, so you can avoid a lot of messy synchronization logic by simply refreshing data the next time the Watch or iPhone app becomes active. Unfortunately, seeing the simulator screens right next to each other makes it tempting to build complicated synchronization logic. Yeah, maybe I did. But I’m not telling.
  • While you can’t construct interface controllers and controls programmatically, you can be clever about how you hide/unhide elements. It has become a common WatchKit practice, for example, to build a full-page label that can be unhidden if there is an important message to display. Or, if you have two layouts that you need to choose from programmatically, you can include them in top-level groups and hide/unhide them as necessary.
  • Remember that each screen touch and interface update requires round-trip communication between the Watch and the iPhone. Code accordingly.
  • As WatchKit interface elements are write-only (they only have setters), it is handy to keep track of values that you’ve already set so you don’t have to set them again. WatchKit tries to help by coalescing values and only sending final values at the turn of the run loop, but you can assist by tracking your own values too.
  • While there is no built-in activity indicator control, you can show a series of animated images while long-running processes (like image transfers or downloads) are taking place. Simply hide the activity indicator image when the operation is complete. Update on 5/3/2015: I just released a JBWatchActivityIndicator project on GitHub that makes it easy to create activity indicator image sequences. It also includes some pre-rendered Apple-like sequences.
  • Be sure to download and review the Apple Watch Design Resources. In addition to helpful color and size recommendations, they also include high quality bezel images that you can use for screenshots in your marketing. While I’m on the topic, it’s worth noting that the screenshots you submit with your app cannot include bezel images.
  • Many developers have reported frustration with images that display properly in the simulator but not on an actual watch. In fact, this has been the source of many app rejections. The problem appears to be related to file naming and “loose” image files. The safest solution seems to be including all images in an assets library in the Watch App (not the extension). This is what I did in my Watch app, and I recommend that you do the same.
  • While it is a very common request, there is no programmatic method to launch your iPhone app in the foreground from the Watch (even though there are methods that work in the simulator). Consider Handoff instead.
  • Local notifications require setting the soundName property to generate Taptic feedback and an audible chime.
  • The simulator is a good start, but it is critical (much more than with an iPhone or iPad app) to test your app on actual hardware.

Good luck with your Watch app!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *