The online home of John Pollard

watchOS 2 Complication Update Issue

Unfortunately there was a bug in v2.0.0 of Count The Days Left where the watch complication wasn’t reliably updating itself.

Fortunately the issue was interesting enough to blog about :)

What I believe was the issue - not 100% sure but it appears fixed now - is that a watchOS 2 app only has a limited budget of time where it’s allowed to update itself.

In the getNextRequestedUpdateDateWithHandler function in my complication data source class, I was telling the watch to update once an hour. This seemed a reasonable compromise (without thinking much!) where the watch would update itself once an hour automatically, even if the options for the countdown didn’t change - which forces a complication refresh.

However I believe that once an hour is too frequent. If you read the Apple Documentation closely, it says:

At the end of a scheduled update, ClockKit calls the getNextRequestedUpdateDateWithHandler: method to get the time for the next scheduled update. Specify a date as far into the future as you can manage. Do not ask the system to update your complication within minutes. Instead, provide data to last for many hours or for an entire day. If your budget is exhausted, the next scheduled update does not occur until after your budget is replenished.

Now obviously my count only updates once a day, so I can return in getNextRequestedUpdateDateWithHandler to next update itself at the start of tomorrow, which means it will only automatically update once a day (but should be correct all that day!)

It appears to have worked on my watch overnight, so I’m sending a v2.0.1 to the app store today (Wed 21 Oct 2015). The last version made it past review and into the store in about 2 hours(!), but it’s unlikely to be that quick again.

Update (2015-10-29)

Turns out it was more complicated than I thought :)

Even after the fixes above the complications still intermoittently failed to update overnight. I just think the “automatic updating” is just a bit flaky.

Anyway, I’ve much improved things by putting in an additional call to update watch complication every time the iPhone app, today widget or watch app is access. I also added a background app refresh handler in the iPhone app to call the same update method when ever it is intermittently called.

All of these calls shouldn’t be too frequent - so should have a neglible effect on battery life I hope - and guarantee as much as I can the complication is up to date.

Another version has been submitted to the app store, and hopefully this one will fix the issue.

N.B. All code can be seen on GitHub

Count The Days Left v2.0 Summary

All done! A final few tweaks and v2.0.0 of Count The Days Left has been submitted to the app store for review.

Things I learnt

Developing for the watch is fiddly

watchOS 2 is a pretty big change from the first version, with the extension code now running on the watch. In theory this should help the performance of watch apps - and I believe it does - but some thought needs to be put into to how you manage your data flow between the phone and the watch to optimise this.

I also struggled for a while understanding the best workflow for testing watch apps, using a combination of the Xcode simulators and the watch itself, but definitely learnt a lot along the way what works best.

Is Swift ready for prime time?

I really like Swift as a language compare to Objective-C (although now I’m used to the crazy square bracket syntax I don’t mind the latter any more), but I don’t feel as though Xcode is quite as reliable when building a Swift project compared to an Objective-C one.

Sometimes the dependency detection on what changed seemed a bit flaky, or the compiler would say a setting is missing from an Info.plist file when it was actually there (and a recompilation “fixed” the issue).

None of the problems are major, and won’t put me off starting a new project using Swift. However I’m hoping things get fixed in later versions of both Xcode and Swift.

What next?

Right now I think I’ve reached the end of features I can add to the app, at least until iOS10/watchOS3 come out and offer some new things to play with.

However I’m always open to suggestions, so drop me a line at johnp@bravelocation.com or reach out on Twitter to @yeltzland if you have any thoughts.

v2.0 Articles

N.B. All code can be seen on GitHub

watchOS 2 Complications Complications

So I decided against adding a glance to the app as promised last time, as it doesn’t really add much value over the app itself, and to be honest I found it quicker to go straight to the app rather than try to scroll through my glances.

Anyhoo, I’ve had a load of fun trying to get the complications working, and after more to-ing and fro-ing finally have it working.

This should have been easy, especially as the built in templates for the smaller complications already have the “progress ring” that matches almost exactly what I want.

Apple’s documentation is OK if a little dry, and there are few helpful examples that can be found with a quick search.

The one key point I found was that the Xcode project settings where you set the data class to be used for the complications data isn’t quite correct.

What you need to do is set the class in Info.plist to be: $(PRODUCT_MODULE_NAME).ComplicationsDataSource

not:

ComplicationsDataSource

as the target\General\Complications Configuration would suggest.

So I think v2.0.0 is just about done! I’ll need to generate the App Store screen shots and text as usual, and then hopefully it’ll be released in the next couple of weeks.

I’ll probably write a summary post soon too to summarise all these ramblings :)

N.B. All code can be seen on GitHub

Testing a watchOS 2 app directly on the watch

TL;DR

This is a fairly long story with a lot of false trails, so if you just want the best way of testing on the watch I’d skip to the end

The app never seems to start on the watch

In my last post I mentioned that although the app worked fine in the Watch simulator, it was “slow” on the watch - in other words it never seemed to load at all.

I first assumed the problem was that the app initialization was taking too long, so I tried a few things including:

  • Adding in-memory caching to the data model class to (maybe) improve subsequent reading of user settings post initialization
  • Changing the data model to be held in the App/Watch delegate class so it’s only initialized once per app
  • Moving the watch session connection code to run on a background thread so it didn’t block the UI thread loading the initial view controller

All of these changes made a difference in the load time on the simulator, but still no joy running on the watch :(

Pushing a TestFlight app to the Watch

I finally realised I must be missing something, so after doing a bit of research I found out that only signed versions of a Watch app will run on the actual device.

This made a lot of sense, so I followed the further advice of pushing an archived version of the code to the App Store, and then installing it on my phone (then watch) via a TestFlight install.

Now this actually worked, but it took me ages to actually get an acceptable archive working.

The problem was I was trying to reuse the code in both iOS and watchOS using a code library, and I just couldn’t get the archiving to work and sign the various bits of code correctly.

This is crazy complicated (for me at least) and to be honest whenever I’ve used a dynamic library other that through pods I’ve always had issues. Not sure if it’s me or it really is a complicated problem, but it seems the worst part of iOS development compared to most other languages and platforms.

In the end I gave up with the library approach on the watch, and just added the shared code files to the Watch Extension target directly which did the trick.

Solution: Add watch to provisoning profiles

Although the “TestFlight” solution worked, clearly it’s non-optimal as there’s a long iteration time to test any changes on the actual watch.

Some more research found this excellent post by Jared Sinclair on how to solve this problem by adding you watch to your provisoning profiles.

I won’t repeat Jared’s post here, and it’s sort of “obvious once you think about it” (which clearly I didn’t!), but I guess I did learn a lot along the various false trails - which is the purpose of the exercise :)

Next Steps

  • Add a glance view
  • Add watch complications
  • Ship it!

N.B. All code can be seen on GitHub

Synching user settings on watchOS 2

In my last post I mentioned that watchOS 2 doesn’t have access to NSUserSettings, which isn’t entirely correct. The watch can have access to user settings, it’s just it can’t directly synchronise them when they are backed by iCloud.

This means if we change the user settings on the phone we need to push the changes to the watch ourselves.

This isn’t too tricky to do if you follow the instructions on the Apple Developer site about using WCSession to set up a sharing session between the phone app and your watch extension.

As I wanted to continue sharing code between the two apps, I’ve had to add a slightly hacky onWatch property to the settings class so it:

  • Only uses iCloud synchronisation on the phone
  • If a setting is changed on the phone, it sets up a WCSession and uses it to send a Dictionary containing the changes via transferUserInfo()
  • The watch will receive those changes in didReceiveUserInfo() which it can then use to update it’s local settings

There’s a small “code smell” of adding in this onWatch flag just so I can reuse some code in this way, so I may come back and revisit this code later.

According to the docs transferUserInfo() queues any transfers up to send to the watch when a connection is available.

The solution seems to work pretty well - in the watch simulator at least! On the watch itself the app does seem to run rather slow, but TBH that seems a problem with most watchOS apps I’ve used. More investigation needed!

Next steps: Start tidying up the UI in the existing app, and see if we can improve the performance on the actual watch.

N.B. All code can be seen on GitHub