Driving User Engagement with watchOS 3

New Features in Realm Obj-C & Swift

We’ve released version 2.7 of Realm Objective‑C and Realm Swift! In this release, we’re introducing improved permission APIs, faster reconnects, password change and applying a few bug fixes to keep your apps running strong.

One of the most interesting aspects of the Apple Watch is the fact that it is a new opportunity to engage with and delight your users. What’s different about these interactions, compared to the phone, is that they should be as short as possible - 2 seconds! What can you do in 2 seconds?! Using complications, notifications, and quick access to apps in memory, we’ll take a look at not only how to create and use each of these features on the watch, but also the best way to delight your users with each! After this try! Swift NYC talk, you’ll walk away with some new strategies on how to increase your app’s indispensability through these awesome watch features.


Introduction (0:00)

I am so excited to be here to talk to you about driving user engagement with watchOS 3. Before we get started with watchOS 3, I want to talk about what user engagement means to all of us. Usually when we think of a user - let’s call her Hectorina - when we think of Hectorina interacting with our apps, we think of her literally directly using her phone, right? And this could be her checking her weather app or looking up the best Italian food in New York or checking out pictures of her friends on Instagram. There’s a lot of different varieties. But essentially, it all boils down to her literally directly interacting with her phone.

But what about all of the types of user engagement where she might not be looking at her phone? Like when she’s glamming herself up, getting a new hair cut or lighting up the dance floor with some awesome Salsa moves? And maybe she’s catching some waves. In all of these cases, she’s not going to be interacting with her phone. How are we supposed to be able to get her to do this and how are we supposed to be able to give her what she needs when she’s not even interacting with her phone?

Luckily with watchOS 3, we can use three different new features that will help drive us to do this. These are complications, background tasks, and local notifications.

Complications (2:04)

Complications are the widgets on the watch face and it’s a term borrowed from real mechanical watches. Back in the old days, they would create these complications that would go on your watch face that would allow you to do things other than tell the time. And they were fairly complicated to create. I think the most expensive watch out there had 32 mechanical physical complications. It was a cool term that was borrowed by Apple for this.

The interesting thing with watchOS 2 is that most of the watch faces had about two to five different complication slots; so you can see here, this is the modular watch face and it has about five slots. A lot of the times users would be limited to only two to five slots for complications. Fortunately, now with watchOS 3, Apple’s made it a lot easier to swipe in between all of the different types of watch faces out there. That means that users have an unlimited amount of complications now. It’s super cool and really useful for us as app developers.

Now, because of this, a lot of times users are probably going to be theming their watch faces so you can think of this as a way for users to create themed watch faces that they might not use daily, but would be very useful in the moment.

This slide, for example, is showing a workout-themed watch face. You can see here we have a big emphasis on our activity. We have the workout app and the heart rate app in the top two corners as our complications and this would be useful for a variety of different cases. You can think of it for travel, for another example. A lot of us came here via plane or any other source of transportation. You can think of a travel watch face being super useful, something that shows our flight status, maybe the weather in New York and also the local time. There are a lot of different great use cases for these themed watch faces.

The other cool thing about this is that there are two different complications here on the top left and right corners which are just static images that are shortcuts over to two different apps. This is another way that allows users quick access all of the different apps that they have on their watch. To implement a complication, this is all that we need to do.

import ClockKit

class ComplicationController: NSObject, CLKComplicationDataSource {

	// MARK: - Timeline Configuration

	func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
		handler([.forward, .backward])

	}

	func getTimelineStartDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
		handler(nil)
	}

	func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
		handler(nil)
	}

	func getPrivacyBehavior(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) {
		handler(.showOnLockScreen)
	}

	// MARK: - Timeline Population

	func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
		// Call the handler with the current timeline entry
		handler(nil)
	}

	func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
		// Call the handler with the timeline entries prior to the given date
		handler(nil)
	}

	func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
		// Call the handler with the timeline entries after to the given date
		handler(nil)
	}

	// MARK: - Placeholder Templates

	func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
		// This method will be called once per supported complication, and the results will be cached
		handler(nil)
	}
}

Don’t worry, it’s not this bad, actually. The top half of this is primarily configuration code. It’s one of those things that you set once and forget about, as long as your app’s functionality doesn’t change. The main thing that we want to worry about is the timeline population code here. Let’s dive into that a little bit more.

In order to create our timeline population code, this essentially allows us to create a timeline of data that we’ll use to present on our complication. The timeline concept is this thing that allows us to create a whole array of data of what we want to display at certain hours throughout the day. You can see here, this is a great example of a weather app where it shows the temperature changing throughout the day. What’s interesting about this is that it also allows us to use this feature called time travel.

Time travel, if you’re not familiar with it, allows you as a user to scroll the digital crown on your watch and be able to advance the hours so that you can go forwards and backwards in time and check out what’s happening in the future and in the past. What is really interesting about this, though, is as of the latest betas for watchOS 3, this has become an optional thing. It used to be required. And for all users, it’s actually turned off by default. What’s cool about this for us as app developers is that you may not exactly need to support time travel and timelines in general, in this case.

import ClockKit


class ComplicationController: NSObject, CLKComplicationDataSource {

    // MARK: - Timeline Population

    func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
        // Call the handler with the current timeline entry
        handler(nil)
	}
    
    func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping
([CLKComplicationTimelineEntry]?) -> Void) {
        // Call the handler with the timeline entries prior to the given date
        handler(nil)
    }
    
    func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping
([CLKComplicationTimelineEntry]?) -> Void) {
        // Call the handler with the timeline entries after to the given date
        handler(nil)
    }
}

So instead of having to support all three of these delegate methods, it goes from three down to just one.

func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping
(CLKComplicationTimelineEntry?) -> Void) {

        // Call the handler with the current timeline entry

        handler(nil)
    }

If you remember that huge block of complication stuff that we had to worry about, most of it was just configuration, and now this is the only method that we really have to support.

How do we create a complication? We’ll first create a timeline entry and a complication template. Let’s take a look at what templates look like. Templates are just various ways of displaying information to our users based on different styles of watch faces.

You can see here, there’s a version of the modular small watch faces or complications as well as some various other ones depending on the different styles of watch faces that you can see. There’s a variety. You have both images and text and combinations of both.

Let’s take a look at what the modular large template looks like. You can see this is the modular watch face here on the right and it’s the largest complication here in the middle. Breaking it down even more, you can see with the modular large complication, it has three different lines of text, the header at the top and the two lines of body text underneath it. This is all we need to do for that template.

func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping
(CLKComplicationTimelineEntry?) -> Void) {
        // Call the handler with the current timeline entry

		let template = CLKComplicationTemplateModularLargeStandardBody()
		template.headerTextProvider = CLKSimpleTextProvider(text: 12:00-2:00PM)
		template.body1TextProvider = CLKSimpleTextProvider(text: "Lunch with Lucas")
		template.body2TextProvider = CLKSimpleTextProvider(text: "Saru Sushi")

    	let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
    	handler(timelineEntry)
}

We want to specify which type of template it is, that modular large one as well as the three lines of text underneath it. We’ve made it really easy and to exactly replicate the lunch with Lucas here on the bottom.

I have simplified this a little bit. You most likely will want some dynamic content for your complications. But if you remember back to this screen where we saw just two static images that allowed you to launch different apps here, if you’re planning to do this with your complication, then it can be as easy as just that static text before.

Background Tasks (8:42)

With background tasks, there’s a really cool new feature in watchOS 3 called the Dock. What this allows us to do is to scroll back and forth between our ten most favorite watch apps and launch them instantly. The other cool thing about this is that while users are scrolling through their apps, they can update in the background and without having to tap into any of these, you can get the latest updates and notifications from your apps without having to do anything through background tasks. Let’s see what kind of background task there are for us.

We have our basic application refresh background task, Snapchat refresh, URLSession, as well as watch connectivity. With application refresh, this is the first thing that we need to do to get started. Usually you’ll start an application background task in order to trigger some of your other ones. From here you can trigger a Snapchat or you can trigger an NSURLSession pull from the background.

Moving on, we have our Snapchat refresh. This allows you to make updates to your app’s UI and you can push, pop, and present view controllers from this. It’s a really cool way to interact with your UI elements all in the background and be able to display this up-to-date interface to your users immediately.

URLSession is fairly similar to a background URLSession task that you do in regular iOS development. Here you can trigger NSURLSession to go out to the server and pull any updated data that you want to show to your user.

And last, but certainly not least, is our watch connectivity refresh background task. For those of you who aren’t familiar with it, watch connectivity is a way that allows you to transfer data between your watch and your phone via its Bluetooth connection. This allows you to grab the latest data between your two devices in the background.

You might be wondering, why we would need a background task for this if we already have NSURLSession? Why do we need to do this on top of that? If we want to be good developers and good citizens, then if we’re using the same data on both devices, we want to make sure that we’re pulling that data from the server only once and then transferring it between your two devices because we know that our users might not have unlimited data and so all of the bandwidth that they have is very precious. So make sure you keep this in mind as you’re creating these background tasks for your users.

Local Notifications (11:35)

Local notifications are very similar to push notifications, but they’re scheduled locally on the watch and it’s handled by the new user notification center which is cool and allows management of duplicates sent to both devices.

// Create the content
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: "Hello!", arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: "Hello_message_body", arguments: nil)
content.sound = UNNotificationSound.default()

// Deliver the notification in five seconds.
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 5, repeats: false)
let request = UNNotificationRequest.init(identifier: "FiveSecond", content: content, trigger:trigger)

// Schedule the notification.
let center = UNUserNotificationCenter.current()
center.add(request)

Here’s how we can create a scheduled notification. First, we’ll want to create the content. I want to create a notification content and then set the content title, body and any sounds that we want to associate with this particular notification. From there, we’ll schedule how much later in time we want this notification to be triggered. Here you can see we’ve set it to be about five seconds later. And lastly, we want to schedule the notification with our new notification center in order for us to be able to trigger it at the appropriate time.

Another thing that I’d like to cover of local notifications is this really cool thing that allows you to create custom responses for your notifications. Again, another way to drive this user engagement without users having to go into their app is through notifications and allowing them to complete any task that they have through these little triggers on their watch itself. We have a to do app that allows us to both complete or snooze a to do task. Let’s take a look at how to do this.

func userNotificationCenter(_ center: UNUserNotificationCenter,
										didReceive response: UNNotificationResponse,
										withCompletionHandler completionHandler: @escaping () -> Void) {

	if response.actionIdentifier == "Complete" {
		//Handle response here
	}
}

We’ll want to first handle this in the userNotificationCenter didReceive response method. All we need to do within this response is grab the response, check the action identifier string and if it matches the response that you’re looking for then you can handle it appropriately here.

Conclusion (13:53)

That’s pretty much it. The bottom line is that you can use complications, background tasks and local notifications in order to make your app completely essential without the user having to do anything on their own. What’s really important about this is it’s a great way of supplementing your current iPhone application with all of these extra things because now your user, without having to do anything, gets all of these extra benefits.

Now your app goes from something that might have been kind of cool before to something that is absolutely essential for your user. If you’re interested in learning more about watchOS 3, I wrote a few blog posts on some of the key takeaways from WWDC ‘16 so I would highly recommend that you read these. I’ve also included some really cool hand scribble notes too for you, so if you’re more of a visual learner, that’s the way to go. Thank you so much.

Driving User Engagement with watchOS 3 Resources


Kristina Thai

Kristina Thai

Kristina Thai is currently an iOS software engineer at Intuit. She works on the QuickBooks Self-Employed iOS app. Kristina is an avid blogger at kristina.io and spends her time writing iOS and watchOS development tutorials and blogging about her early career experience. She started her engineering career after graduating from the University of California, San Diego with a degree in Computer Science. Fun fact: she follows more animals on Instagram than people.

Transcribed by Hilary Fosdal
Edited by Curtis Chen