Limitations of Dates and Timers on WatchKit

WatchKit was announced today and I've been taking an early look at it. I've been excited about building a version of Runtime for the Apple Watch even from the time I thought it would be called the iWatch, so seeing what it would be possible to build with the first version of WatchKit has been a lot of fun. While this is clearly only the beginning of what we can do on the watch, if you're familiar with programming for extensions then you have a pretty good idea of what to expect. Thanks to the Today Extension I built in Runtime for iOS 8, I had a working prototype of Runtime for the Apple Watch up and running in under an hour!

Because your iPhone is required to be present for third party apps and GPS connectivity, the core of a running app on the Apple Watch is going to be showing users stats about their run at a glance. It's so amazing and powerful that the watch can display UI simply by the user lifting their wrist. This is perfect for runners because you don't have to futz with using your other hand to tap a button on the watch. Just raise your hand, read your time and pace, and continue on your way. Naturally for my Runtime prototype I decided to tackle this use-case first.

WatchKit includes two rather amazing new controls that previous required all sorts of custom logic and intricate programming to create on iOS: WKInterfaceDate and WKInterfaceTimer. The top label below is a Date, the second label is a timer, and the third is just a normal label.

WKInterfaceDate is a class designed to show a time and date in a label. The time and date automatically update on a per-second basis. The label can be formatted using any custom date format string to show or hide days, minutes, hours, seconds, years, AM/PM, etc. It's visual style can also be customized with different fonts and colors. It's really nice, and completely encapsulates what would previously have required tons of code from developers. I hope it makes its way back to iOS. In an interesting twist, WKInterfaceDate doesn't actually accept a date object as a parameter. Instead, it accepts a calendar and a time zone, showing it's intended to be used to display the current date (or perhaps a future or previous date) to the user.

WKInterfaceTimer is similar, but is designed around a timer instead of a date. It's slightly less customizable than it's sister class, providing only check boxes to choose which calendar components are visible and which are not. It also provides a selection of pre-defined styles to pick between numerical and textual display of time information. It's the difference between "6:42" and "6 hours and 42 minutes".

The real benefit to both of these classes in the context of WatchKit is that their behavior can be pre-defined and updating their contents doesn't require any interaction with the WatchKit extension running on the user's iPhone. Imagine if the only way to display a timer were to fire a refresh timer on the extension and push those updates to a label on the watch? What a terrible experience and drain on power that would be. This is clearly a much better solution.

These classes clearly do what they are designed to do very well, but they do have limitations. I want to document these limitations and propose a few ways that they can be improved. My goal is to help provide feedback to the WatchKit team early in the process. As we saw with Swift, the team addressed a huge amount of feedback and shipped a high quality version of Swift in the 1.0 release. I'm less optimistic that WatchKit will be heavily changed before it's released, but I want to provide feedback as early as possible in the hope of increasing the odds this will happen.

So, here goes. These are some issues I've noticed with WKInterfaceDate and WKInterfaceTimer.

 

WKInterfaceTimer Does Not Support Counting Up (rdar://19024346)

I believe the WKInterfaceTimer is intended to work both as a countdown and count up timer, but currently the timer only supports counting down. The control description in interface builder actually states "Timer - Displays a string that counts up or down to a specified time." But from the detailed class description online, it is clear that the control is currently implemented as a purely countdown timer.

The issue here is that really the only way to build a count up timer that starts from 00:00.0 and counts up is through this class, so it needs to support this.

The only attribute this class has so far for controlling it's behavior is the date. You can set a date (which the header states will be counted up/down toward). Here's how that actually seems to work now though.

If you specify a date with a positive time interval from the current date, say, 3600 seconds (one hour) from now, the timer will start at 59:59 and counts down like you would expect.

If you specify a date with a negative time interval from the current date, say, -3600 seconds (one hour earlier), the timer will start at 1:00:00 and begin counting up, sort of like you would expect. But this behavior isn't really defined. I haven't actually waited an hour to see if it would stop, but I'm still confused why it would start at 1:00:00 instead of 0:00:00 and count up towards 1:00:00.

Let's say I wanted to implement a count up timer that counted up to a maximum of 12 hours. Given the behavior above, I'd probably expect to input -(3600 * 12) as the time interval offset for the WKInterfaceTimer's date parameter. But that results in a timer starting at 12:00:00 and counting up. :(

The other alternative I could see is specify a date of zero, or possibly -1, as the offset to the date, in the hopes that those would result in a timer that counts up starting from 0:00:00. Unfortunately, neither of those result in the desired behavior either. In both cases the timer simply displays "1" and stays there. The same is true until you specify a negative offset greater than or equal to 60. In other words, the timer really doesn't feel like starting anything below a minute of precision. If you specify -60 as the time interval, you'll get a timer that starts at 1:00 and counts up from one minute.

I'd like to see a cleaner interface to the WKInterfaceTimer class that allows you to specify a boolean for "count up or down", for example. At the very least, I'd like to see a documented and working count up behavior that allows a 0 or -1 time interval that supports a count up timer starting at 0:00 or 0:01.


WKInterfaceTimer's User Interface is Not Sufficiently Customizable (rdar://19024361)

The timer class in WatchKit provides basic support for a few pre-defined date formats, and for which possible calendar components (days, hours, minutes, seconds, etc.) the timer will show. This is actually a pretty nice form of customization, but it's not quite deep enough. Some timers may wish to show hours or minutes even when their contents are zero, or format the text in different ways.

This level of customization is actually available in the WKInterfaceTimer's sister class, WKInterfaceDate, which takes a customizable date format string as a parameter. My proposal is that the WKInterfaceTimer should take an optional date format string as a parameter as well. If a date format string is present, it should use that format for displaying the timer. If the format string isn't present, then use the default customization parameters.


WKInterfaceDate Time Zones Do Not Have Second Level Accuracy (rdar://19024376)

In a last ditch effort to create a properly formatted count up timer on a Watch App, I turned to the WKInterfaceDate control. My goal was to create a WKInterfaceDate control that was configured to show a time in 00:00:00 (hh:mm:ss) format. The control would also be configured with a calendar and time zone to start with a displayed time of 00:00:00 and count up one second at a time from there. I know it's not the intended purpose of this control, but it seems like a reasonable customization to make.

However, the control doesn't seem to respect the seconds component of the time zone. I attempted to create a NSTimeZone where the seconds from GMT are started from zero, and with the current GMT time subtracted away from that. So if the time were 1:07:26 AM, GMT, then the time zone would be created from a GMT offset of 4046 (3600 + 420 + 26).

This actually works beautifully well for the hours and minutes. They will correctly line up at the 00:00 position you would expect them to. But the seconds will not. No matter what you supply as the seconds offset for the time zone, even say -5, the seconds on the WKInterfaceDate always match exactly to the seconds of the system clock. There doesn't seem to be any way to provide a time zone or calendar which changes the seconds of the displayed time/date from that of the system. That means that it's not possible to use this control for displaying a time as an offset from a specific second value. But it would be if the time zone seconds offset were honored.

My proposal here would be to make the time zone's second component honored by the control. If the time zone supports initialization with second level accuracy, and the date control supports presentation at a second level accuracy, then the two settings should work correctly together.


WKInterfaceTimer and WKInterfaceDate Don't Support Milliseconds (rdar://19024387)

The default WLKInterfaceTimer class does not have an option to display milliseconds in the label for the given date as either a count down or count up timer. It also doesn't support a custom date format string, so the option to add this information isn't there.

The WKInterfaceDate actually does support a custom date format string, so supplying a format string that includes milliseconds actually is an option. However, when you supply milliseconds to the format string, the millisecond value is not updated. The WKInterfaceDate control's internal timer seems to be firing once per second, which means it can't update the label's contents on a millisecond basis anyway. 

I'd really like to see millisecond support because it creates a more accurate and better overall experience for runners to gauge their speed and run duration. It's also just a better visual experience to feel like the watch is actually working if the user can see milliseconds counting up. Without that, it feels like the watch is running slow, or that the user is running slow. I do understand the power tradeoffs involved with timers firing at shorter intervals to make this possible, and I know it's not ideal, but some support for it in at least a limited fashion would be nice to have.

 

I'll be experimenting more with WatchKit over the next few weeks. I fully intend to do my best to ship a compelling Apple Watch app for Runtime when it's released. However, issues like this do show that it's still only the beginning stages of what we can do on the watch. I expect that we'll have more freedom and flexibility over time. My hope is that we'll get just a little bit more of that before the Watch is released, so that we can deliver really compelling experiences to users.