Nearest Bus Post Mortem Part 4: Today Extension

This is a multipart post-mortem-style post series on the design and development of Nearest Bus. I'm planning on covering the architecture, design, code, widget (this post) and possibly some on adding a watchkit app, once I finish that off.

My wife has long joked that Nearest Bus should be called "Run or walk", as I'd usually check it when we were around the corner from the bus stop - should we be running to the stop, or did we have time to keep walking?

However, unlocking my phone, finding and loading the app, waiting for it to find the stop and load it, it all takes time.

In iOS8, Apple added Extensions, which allow you to build your own keyboards, sharing extensions, and Today Extensions, which sit in the "Today" notification area than you can pull down from the top of the screen. It's an area which is designed for glanceable data - fast to load, specific to the user or the users current situation, and usually small.

Today Extension

Pretty much perfect for showing when the bus is coming!

Xamarin have added support for Extensions (I prefer Widget, but that's the proper name I think), so it was fairly easy to fire up a new Extension and reuse the common library. The focus of the extension is to show the nearest 4 favourite bus stops, and only show the route and times.


At it's core, a widget is a seperate app, which is contained within your existing app, but only for packaging purposes. It has it's own assets, it's own binary, everything. The only thing they can share with the host app bundle is code, by using a shared library, but the Xamarin model doesn't yet support iOS shared libraries (which is also only supported on iOS8), so you have to duplicate your code in 2 binaries.

From a .NET point of view, tho, this is easy! Just make a new assembly, and import it into both the app and the extension. Easy. Well, almost: more on that in a moment.

Preferences (NSUserDefaults) can be shared by an App Group, and the Keychain is also available. You can also setup a shared folder to move data between your app and the extension, tho I dont use that in this app.

//this has to be setup in the mobileprofile
const string groupId = "group.com.bigtedltd.NearestBus";
NSUserDefaults GroupedDefaults {
	get {
		return new NSUserDefaults (groupId, NSUserDefaultsType.SuiteName);
	}
}

void LoadFavourites () {
	using (var defaults = GroupedDefaults) {
		string defaultsJson = defaults.StringForKey ("favourites");
		//etc
	}
}

Every time the user pulls down the today notification area, your main UIViewController is recreated and restarted, so make sure any state is kept out of this main ViewController, and can be recreated if the app is killed outright. After it's create, the WidgetCallback is called.

[Export ("widgetPerformUpdateWithCompletionHandler:")]
public void WidgetPerformUpdate (Action<NCUpdateResult> completionHandler)
{
	//Do stuff that we have to to each time the app is loaded
	if (TodayViewModel.SharedInstance.Location.LastFoundLocation != null) {
		LoadFromLocation(//last lat and lon);
	} else {
		ReloadCollectionView ();
	}
    //Tell the OS we have something to show. Or nothing. Or an error.
    // ONLY call this once.
	completionHandler (NCUpdateResult.NewData);
}

This is the "main method" for the widget, in which you can start updates, and eventually tell the OS that you have something - or nothing - to display. The general process is:

  • OS shows the last state of your wiget as a static image
  • Create
  • WidgetCallback is called
  • You work out if you have something to update (something, nothing, error). The screen is updated as needed

You can keep changing the screen after you tell the OS that you have new data - eg animations, or moving things around and updating labels - but if you say "no new data", then the previous image is left there.

Be sure to only call the callback once, and if possible, AFTER you have finished updating the UI. Things get a bit weird if you don't.


Designing a widget is pretty simple. You have a base storyboard, on which you can put anything which doesn't scroll vertically (and Apple recommends not scrolling horizontally either), and then .... go nuts!

Widget Storyboard

I've used a UICollectionView, which is the same class I use in the main app. UICollectionView is pretty much UITableView 2.0, with a lot of the crappy bits removed and fixed.

As your widget can't scroll vertically itself, you can resize the collection view to wrap it's content easily:

public void ReloadCollectionView ()
{
	//Do other stuff, then set the PreferredContentSize
    //StopList is my UICollectionView
	PreferredContentSize = StopList.ContentSize;

}

[Export ("widgetMarginInsetsForProposedMarginInsets:")]
public UIEdgeInsets GetWidgetMarginInsets (UIEdgeInsets defaultMarginInsets)
{
	//remove the border. Go full width.
	return new UIEdgeInsets (0, 10, 0, 0);
}


Working within a widget container has a few constraints, however. The main one is that you are limited to 16MB of memory use. This is a hard limit - a VERY hard limit. If you go over, you are immediately killed. This can be quite difficult to keep within, so keep you images small (prescale them), make good use of text data rather than image data, and architect your app in such a way that anything which isn't needed isn't included.

This last point is especially important for me. I was pulling in Sqlite for the AT data, but I didn't actually use it in the Widget - the data all comes from the favourites, which is just a JSON serialsed blob, so I'm not looking anything up in Sqlite.

So I moved the sqlite-using ATStopProvider into a seperate shared library, which is only used by the app, and removed the dependancy on sqlite from the core shared library. Only one is registered with the IOC container on startup. That stopped me getting killed quite as much, tho I think I'm very close to the limit as it is.


One of the other advantages of creating a widget at this point is that it should be fairly easy to create a WatchKit app down the road a little - and having the bus info as a glanceable screen on the watch would be ideal!

A WatchKit app is just another extension - they work exactly the same as a Today Extension, and the code runs on the phone, with the display "remoting" to the watch. I'll post more once I have the watch app working, as Xamarin now have (Alpha) support for WatchKit and iOS 8.2 apps.

I need to get my head around WatchKit UI design, too, before I start on this. It might have to wait until after the watch is released.

Nic Wise

Nic Wise

Auckland, NZ