Streaming is available in most browsers,
and in the WWDC app.
-
Creating Complications with ClockKit
Making your app's information available to users on Apple Watch is even easier in watchOS 2. Learn how to create custom complications for Watch faces using the new ClockKit framework and see how to provide data through numerous templates using text and images.
Resources
Related Videos
WWDC 2019
Tech Talks
-
Download
ELIZA BLOCK: Hi, everybody, I'm Eliza and with me is Paul Salzman. We will be telling you about complications, what they are and how you can make them. So, complications are small pieces of information that appear right on your clock face alongside the time.
On these clock faces you see here, if we remove the time from the equation, everything left is a complication.
So the ones you see here are all built into the OS, but starting in watchOS 2 you can create your own complications for the clock face and we will tell you how to do that, and I'm going to use the example of building a complication to display the upcoming matches in a soccer tournament.
So let's take a look imagining you already built such a complication at what it would look like to go select it.
So focusing in on the middle clock face here, if you were to Force Touch the screen, you can customize the face, swiping to the right allows you to start tapping on your different complications, and turning the Digital Crown allows you to pick one. So if we scroll all the way to the end of the list, we will see our not-yet-built soccer tournament complication and we could select it and we start seeing the live data displayed on the clock face. How are you going to provide the data to populate this complication? As you can see from looking at the face there is a consistent look and feel to all of our clock faces and that's what part of what makes them pleasant to interact with so we wanted to do this while preserving that consistency.
So your extension which is running on the watch will provide the data for these complications in the form of text and images.
And then the clock face will actually draw it in a way that fits with the rest of the face. So if I were to install this complication on my watch, I will see it right away every time that I raise my wrist. And that's a great thing, and it's one of the great things about building a complication, but it also presents a kind of a challenge because as time passes and the information that's displayed in this complication needs to update, since it's visible as soon as you raise your wrist, that update needs to already have happened by the time the screen turns on. And if you think about it, there could be five different complications showing on this single watch face. There is no way we would have time to launch all of them, pull for new data, potentially involving a network request and have all of them come back with data in time for the screen to come on so to solve this problem, what we're going do is collect the data for your complications in the form of a timeline in advance.
So you will give us a timeline of data, how much of the timeline depends on the density of the data for your application.
And that way as I glance at my wrist throughout the day, every time I look at it, the complication has already updated to display the information that makes sense at that moment. Now, timelines are a really powerful concept and Paul will talk about them later in the session, but I wanted to mention one other thing that this buys us, which is the Time Travel feature. So Time Travel is a new feature we introduced in watchOS 2 which allows you to turn the Digital Crown and right on the clock face you can see what your complications will be showing at different times of the day. So you could peek forward to see when does the next match start or what meeting should I have been at an hour ago and so on.
So to see how that works with this complication, there is no extra work that you had to do in order to make this happen. By providing your data in the form of a timeline, we can just make Time Travel happen for free.
Getting started, so when you make a new Xcode project or convert an old project over for watchOS 2, you can choose to add a complication.
You can check that box, and that will cause Xcode to automatically generate the files that you need and set things up so it's super easy.
There is one other thing you need to do.
If you navigate to your WatchKit app extension's general info pane, you provide the dataSource class which we will talk about later, and you also provide checkboxes for the families of complication that you want to support. So now what are these families of complication? Complications on watchOS look different ways on different clock faces and we have divided this up into five different families and you can choose individually whether to support each one of these families so I want to show you what these families look like.
This is the Modular face, and it has two complication families on it.
The ModularSmall family gives you these square-shaped small complications, and the ModularLarge family is the one, one complication in the middle of the face that can show a fair amount more data so that's the one we have been focusing on with this example.
A number of analog faces also support complications. And these complications are in the Utilitarian families. There is a UtilitarianSmall which kind of fit in the corners of various analog faces and then there is UtilitarianLarge which gets the entire region along the bottom. And finally, on the Color face, there is a single family which we have called Circular Small. Here is what this looks like in code. It's an enumeration named as you would expect.
All right. So when the user activates your complication on a face, they are going to choose it for a particular position that the complication can appear and that position will be associated with a particular family. So you will be told that you are providing data for this family, and at that point, you need to decide how you want to lay that data out. And there is a number of ways that you can lay out your data for each one of these families and I want to show you the collection of templates that our designers have created.
So for the ModularSmall complication family, we have these templates.
There is a whole bunch of different ones. You can have small text, two different rows of text, an image and text, you can do a big piece of text, a big image. These ones along near the bottom here with a ring filling up can show you your progress towards something in the form of a floating point number that can change between 0 and 1 and you can put text or an image inside of that and finally there is a column template which allows you to do something like show a sports score. ModularLarge also has a number of different ways you can layout your data. There is a simple standard three-line template with an optional image.
You can do a column-style template, two different column-style templates, actually.
And finally you could do a template with a large piece of text which is suitable for something like a kitchen timer or a date, many other possible things you could do with that. UtilitarianSmall, most of these guys are flat and they have an optional image in the corner, but you can also have a larger image and you can also do this ring style with UtilitarianSmall.
UtilitarianLarge, there is just one template for this. There is an image that's optional and some text next to it. And finally, CircularSmall has templates that are similar to the ModularSmall ones although with slightly different sizes. So that's the templates that you have access to in watchOS 2. I'm going to take a look at what that means in code. So let's zoom in on our soccer club digital ModularLarge template. This has four pieces.
There is a header image that you can provide.
Header text.
And then there is two lines of body text. So you may have noticed that there is a lot of these complication templates. All of these correspond to a subclass of CLKComplicationTemplate which is the superclass of all of them, and you can choose which one you want to use and fill out its various properties so it's pretty simple.
So this one is the CLKComplicationTemplate ModularLargeStandardBody which is a bit of a mouthful, but it conveys one really important piece of information right in the name, which is that this template is for the ModularLarge complication family. And it's really crucial when you are asked to provide complication data for a particular family that you provide a template that matches that family, and that's why we have built the name of the family right into the name of the class, so there can be no confusion about it.
It obviously wouldn't work to produce a template that looks like this to appear on the circular face.
Now, you may have noticed something else that's a little strange here.
We have an image and three text properties but instead of UI images and NSStrings in the code, we have this imageProvider and textProvider business. So what's going on here? That brings us to our next section, how do you provide images and text, and I want to explain what these classes are for and what they can do for you. Let's start with images.
Here is our complication in the color editing screen. So the user can customize what complications they see and they can pick your complication in the course of that. They can also customize the look and feel of the face. And that includes changing the color.
So when you provide images for your complications, these images need to take part in that same color scheme that the user has selected for their face. So they need to be able to change color as you see.
So there is our soccer ball obediently turning whatever color the user is choosing.
So an image provider is a sort of a bundle of properties that manages to achieve this effect. So you can provide a background image and you provide it as a template image, an image that only contains alpha information and no color of its own. It can be -- the pixels can be whatever color you want, but we are only going to pay attention to the alpha channel.
It will be colored depending on the user's selected color, but you can do a little bit more with this. You can also provide a background image and a foreground image and these will be laid on top of each other. The background image will be colored according to the color of the face and the foreground image will be superimposed on it so you can get a little bit more detail in your images. You can also choose to make the foreground image black.
So let's take a look at the code for this.
You give us a background image. You give us an optional foreground image.
You can choose a background color for your background image. For the most part the color is going to be determined by what the user has picked so this background color represents what the color you would like your background image to be if you can choose and there are some contexts in which that would be honored, but as long as this is appearing on a face where the user chooses the color is will override the color you supply here so this is an optional property.
Okay. That's image providers. So now what about text providers? These are really cool! So I'm really excited to get to tell you about them! When we started out building complications in watchOS 1 we had challenges which were mostly due to the fact that complications were tiny compared to all of the other UI you are used to creating for all of our platforms. Some of them are as small as 44 pixels square, and we are trying to fit information that's of use in this very small space, but it can be challenging to do that without having all of your text truncate. So a good example, so the idea here for text providers is that because the space is very constrained, you want to leverage some of the work that we have already done to figure out how to format and fit the text in the small space.
So we are introducing text providers as a way for you to declare your intentions to us rather than always passing us an already formatted string and then we will handle the details of formatting and fitting that string for you.
So an example is formatting dates.
There is a CLKDate text provider that does this for you and I want to show you what that's useful for.
Imagine you wanted to display the date, Wednesday, September 23rd in one of your complications.
Now, space is really constrained so pretty much in most contexts you are more likely to see something that looks like this, which is obviously not very informative. We have lost kind of the bulk of the information.
So instead of truncating, it would be better if we could fall back on increasingly narrow renditions of this string that were still informative. So, for example, we could start abbreviating some of the elements. You could abbreviate more of them, and if that still didn't fit, we could even start dropping some of the elements instead of truncating, preferable to see Sep23 versus Wed dot dot dot.
And finally, if we had way less space to deal with we could drop down to displaying the day of the month.
This is what CLKDate text provider will do for you.
You have a date you want to display, you have units you would ideally like to display, in this case the weekday, the month and the day. You create a text provider from these pieces and then that very text provider can be attached to one of these templates and it will look different depending on the context. So here it is, in the ModularLarge complication displaying one of the longer renditions of this date.
Here are two of these very same text providers produced with that very same code displayed with widths of various degrees of further constraint and here is the same text provider providing a date in one of these CircularSmall complications and there is no truncating anywhere and there is no work you have to do beyond the code that you see here. That's date text providers.
There is other kinds of text providers as well. The one you are most likely to use most of the time is the simple text provider this allows you to provide arbitrary text.
And but it's better than an NSString because in addition to providing the text you would like to see, you can also provide a shorter version and we will fall back on the shorter version before truncating the ideal version. There is a time text provider which formats times for you nicely. You get the nice small caps that match the rest of the system and will drop the a.m. /p.m. if there isn't room to fit it. As you see in the sunrise/sunset complication which is using this text provider at the bottom.
There is also a time interval text provider. This text provider is good for formatting ranges of times. We use it in the calendar complication and it has some nice formatting features as well.
It will look like this if the first date is in the morning and the second date is in the afternoon. If they are both in the afternoon, it's smart, and it drops the redundant a.m. /p.m. symbol to make this look nicer. It will also fall back on narrower versions if this rendition won't fit. These are useful and we encourage you to use them. There is one more I wanted to talk about now, and before I get to it, let me show you the problem it's solving.
So here you see our moon phase digital ModularLarge complication. And at the bottom of that, the third row in that moon phase complication it's telling us the amount of time until the moonset in terms of hours and minutes. So the moonset is at 2:19 today, which is in 3 hours and 1 minutes from 11:18. So as time ticks by, this string changes at 11:19, it shows 3 hours, at 11:20 it shows 2 hours 59 minutes, and so on.
Now, imagine if you were creating a timeline to populate this complication. You would need to provide a new template for every minute of the entire day. That's a lot of templates.
And it could be even worse because that's the moon phase complication ticking down by the minute. What about the timer which ticks down by the second? Imagine how many templates that is. It's more than we could possibly reasonably cache and it's incredibly wasteful because these strings are derivable from two pieces of information. The date you are counting down to, and the date that it is now.
And we know the date that it is now. That's what we are doing. We're a clock. So we wanted to give you a way to produce these strings without populating your timeline full of just a massive quantity of redundant information, that's what the relative date text provider does.
Here is what it looks like in code if we were trying to produce the third line of text that you see here. We would get the date for the moonset.
We would choose the units that we wanted to display and actually I will show on the next screen there is a lot of different units you can use here.
We would choose a style, and, again, I will show you the possible styles. This one you see here is the natural style.
You would make a relative date text provider out of these elements.
And then you would just set that text provider as your body 2 text provider for your template and the clock face does the rest. It will always display the relative date to the date that you gave us at every given moment without you having to do any further work.
So these are some styles that are available, and as you can see if you look at the natural and style and the offset style, you can get either fine grained relative dates or very course grained relative dates so depending on the date that you wanted -- depending on how far it is to the date you want to display, we can show weeks and days, months and years, so on, all the way down to seconds. All right.
So that's text providers and image providers.
So I want to sum up how it is that you are giving us your content.
You choose a template from one of the number of possible templates, choosing one that matches the complication family you are being asked to provide data for.
And then you populate that with image providers and text providers. And then you are going to hand us a whole bunch of those in the form of a timeline and to talk about more, to talk more about how to form one of these timelines I want to invite up Paul.
Here he is.
PAUL SALZMAN: Awesome! Hello everyone.
So as Eliza mentioned we are going to be gathering our data for your complication in the form of a timeline.
This helps to support two things: the brand-new Time Travel feature and we are going to able to present the user with your content immediately on wrist raise without any delay.
Let's take a look at about how to think about timelines and complications.
We are going to start by creating the weather complication up here on the bottom left corner. We are showing 57 degrees because right now when the watch is showing 10:00 a.m., our forecast says that 57 degrees is the temperature outside for this location. And in fact, we actually have a forecast by hour until 7:00 p.m. today for this location that we can take advantage of. For timelines we don't describe a range we associate the data we want to show with a point in time.
So let's slide these over here. As the clock progresses throughout the day. We will show the most recent data you have provided on the timeline on the watch face.
So as we get past 11:00 a.m. we will update the forecast to 55 degrees.
As we inch closer to noon at 11:59 and 59 seconds we are still at 55 degrees.
Once we hit noon we will update the template.
This works similarly for the Time Travel feature.
As the user navigates throughout the day we will show the most recent data available at that point in your timeline.
So as we get past 1:00 p.m., we're going to update your content to 54 degrees.
The other complication is kind of an easy example because your data matches perfectly to unblocked time on the timeline.
Let's look at something more complex by trying to build the calendar complication here.
If you are lucky your calendar has plenty of gaps throughout the day.
Today I'm going to go have brunch and later I will get a haircut and when I look better I will meet with friends and watch a movie. So we can be naive about this and associate the templates we want to display for these events for they begin and clear them out when the event ends. Let's see how this works in practice.
So at noon today we will show that we are at a brunch, but once brunch is over and it's 1:00 p.m. we no longer have content displaying on the wrist watch.
That's a bad user experience.
What's worse though is we get closer and closer to 4:00 p.m. I have no idea I need to get into my car and get a haircut.
So you never want to tick off the person that's doing your hair.
So now it's too late and I'm going to get a perm and it's not going to work well. Let's fix this. The first mistake is assuming we should have blocks for unused time frames in the timeline. So let's get rid of those. And showing an event isn't useful for a calendar complication.
We should put the templates at the end of the previous event so you have adequate time to get to your next event. So the first event over here actually, there is no previous event so it might be useful to actually tag it at midnight to you get adequate warning in the morning when you wake up and on the right we should let users know there is nothing they should be worried about for the rest of the day with an indication there is no more events.
How does this look? At noon just like before we will know we are at brunch but once brunch is over we have adequate heads-up to know we need to get a haircut.
We will meet with our friends because we didn't miss any events and when we are done watching a movie we can go home with knowing we didn't miss out on anything else. So how do you get the data points into your code? You will use the complication timeline entry.
So Eliza described earlier generating templates using text and image providers and all we need to associate with that is an NSState.
We will stuff the objects into the CLKComplication timeline entry.
When you hand that to the clock face we will inspect the date and make a note on the timeline so we know to display the template when you reach that time. You can see in code what the object looks like with the Date property and complicationTemplate property. So how do you actually communicate this data to us? You will have an object in your project that implements the CLKComplication DataSource protocol.
This object is annotated in your Xcode WatchKit extensions target settings as Eliza showed you during setting up your project.
There are a bunch of callbacks you will get on the object.
They are generally per complication. And we will pass you a CLKComplication object that has a Family property you will want to switch on. At this point you will decide is this ModularLarge? Which template should I use? Is this ModularSmall? Which text providers go with the template I'm choosing for that? And in addition to passing the complication that you are interested in providing content for, we are also going to give you a handler. And you use this handler to give us the data we have requested and let us know you are done running. This is very important because you are going to have opportunities to refresh your content in the background. We want to know when you are done running.
So let's start building up our timeline. You will see we have our clock face on the left, your extension on the right and the timeline we want to build across the bottom. Inside of your extension you will have your complication controller object. Now, this is the default object that Xcode will create for you that implements the ComplicationDataSource protocol. This is the object we are going to be communicating with.
So to get started, we are going to ask you which timeline entry should we be displaying right now.
So you will package up a timeline entry and send it over the wire via the handler, and then we're going to add it onto the timeline right then. What's important to note with all of these questions we are asking you is that we are basing the next questions on the information you have given in the previous one. So we are going to be incrementally building this timeline out. We want the data to be super accurate. You don't want to blindly tag the current NSDate for this entry. If it's 10:30 a.m. and we want 10:00 a.m. forecast data you should tag this complication timeline entry with 10:00 a.m. So the function we will ask is GetCurrentTimelineEntry ForComplication.
Again, we are going to pass you a CLKComplication. This has a Family property you should switch on to decide which template, which text and image providers you want to supply, and a handler that takes the timeline entry that you should call when you are done with this callback.
So now we have your current entry and we need to start fleshing out your timeline to the future and the past. Let's start looking to the future.
We are going to ask you incrementally what timelines you have after a specific date. This date will generally be based off of previous data you have handed us and what's still in our cache.
So we will hand you this date and you will package up an array of CLKComplication timeline entries that start after this date non-inclusively.
A good rule of thumb is charting a day's worth of data. When you send this data over the wire, we will add it to your timeline and if we feel we need to cache more data we will ask you again. In which case if you have more data to give us you happily provide the array.
Let's say we keep asking you and there is no more content to show. You can pass an empty NSArray or nil and that will give us the hint to leave you alone. As time progresses all of your entries will be further and further into the past so it's possible to increase our cache, we might have to query you again in the future.
So the future we are calling here is getTimelineEntries ForComplication. We are always going to pass you the complication we are curious about, so look at the Family property.
And then, of course, we are going to give you the after date. This is the non-inclusive date. You will package up the adjacent forward-looking timeline entries from this date.
And to make sure we don't get overloaded with data, we will pass you a limit parameter here.
So don't put any more content into the arrays than this limit provided here.
If you do, we will remove them in a way we are not going to guarantee won't change and you are not going to find out what that means.
And then, of course, a handler that takes this array.
And corresponding to go into the future we have the before date feature of this function that helps flesh out the past. So depending on the type of complication you are providing your needs for Time Travel may vary. Whether a complication that only provides future forecast doesn't want to Time Travel into the past and a stock complication can only Time Travel to the past because we haven't perfected looking into the future for that.
So we will ask you on setup which directions you support.
So for the case of our weather complication, we will say we only support the future and that way our timeline will only look forward. If the user Time Travels into the past we'll actually dim out your content so they know there's nothing there to look at. Similarly for the stock complication, you may say you only provide the past. And we will dim in the other direction.
It's possible that you don't want to support Time Travel, but you still want to get contents onto your watch face.
In that case, you want to supply none as an option. We will still show your content but as soon as we enter Time Travel we'll actually dim it out.
It's important to note that you might know some of the content you want to show in the future. We will ask you, even though you're not supporting Time Travel forward, for data we might possibly be able to cache. We will never ask you about the past because right now time doesn't travel backwards.
And if you want to a truly bi-directional Time Travel experience you can support forward and backward.
So the function we will call to get this information during setup is getSupportedTimeTravel DirectionsForComplication. We will pass you the complication we are asking about as well as a handler that accepts these directions.
Now, giving an indication to what directions you support may not be the full story for your Time Travel complication. For instance, our weather complication only had a forecast up until 7:00 p.m. but Time Travel goes beyond that. So let's take a look at what happens. As we Time Travel forward to 4:00 p.m. we still have data. All of our complications are updating. But once we get to 7:09, just past 7:00 p.m., we don't want our users to think we have valid data for the temperature in the area so we will dim out your complication here.
So the way that we figure out when to actually dim out your complication is by querying you for the end or beginning of your timeline depending on the directions you support so we will ask you during setup how far out into the future if you are a forward complication, and you will give us an NSDate back.
If you support Time Travel to the past, similarly, you can provide us another NSDate to this question and we will adjust accordingly. We will adjust accordingly. So the function we are going to call to find out how far into the future we should Time Travel is getTimelineAndDate ForComplication.
We will pass you the complication and, of course, a handler that accepts the NSDate for the end of your timeline.
Correspondingly, we have getTimelineStartDate to see when your timeline should begin. So when a user is customizing the complication, the watch face and they want to select your complication, they will select the slot in this case ModularLarge and use the Digital Crown to end up on the San Francisco soccer club complication. You will see here a couple of things about the state of customization.
There is a caption but your complications entry that says your application's name and this is provided by the system.
Now, that we are in the modular large slot we are able to provide a ModularLarge template that describes what our users expect to see and give them the right context for why they should select our complication.
After they select the complication, go back to the switcher and back to the live face. We will start querying you for data and populate the timeline. We call those templates we present in customization placeholder templates.
We will query you once on installation and cache that so we don't have to launch multiple extensions during customization.
So during installation, for all of the complications you support, we will query you for your placeholder template to which you provide us a CLKComplicationTemplate. There is no timeline on the bottom, this isn't happening live while we are using it. And we are not using a timeline entry in this callback right here, it's just a complication template because there is no date to associate.
So the function we are calling is getPlaceholderTemplate ForComplication with a complication and as you are used to with this pattern, a handler.
So now that you are very comfortable constructing your timeline, I would like to bring Eliza Block up to give you a demo of how to construct this in code .
ELIZA BLOCK: Okay.
So I have got a project here that I haven't done very much to yet. We are going to actually build the complication that we have been showing you pictures of throughout the session. So let me just show you a little bit about what's going on with Xcode project. I created a new project, I dragged a couple resources in, including a model that I had previously written to provide this schedule.
And I configured it to work with complications, so if I navigate over here to my -- oops! General settings for my Watch extension. There it is.
If I zoom in, you can see at the top that I have got my dataSource class. It looks a little ugly in Xcode at the moment but if you click it you can see it is pointing to our complication class.
And then I have, for now, unchecked all of the supported families except for ModularLarge just to make things simpler in the demo we will just focus on building the ModularLarge complication. We recommend that you do try to support as many of these different families as you can because your users will be interested in choosing different faces and so the more of these families you support, the more likely they are to be able to use your content in their watch face.
So when you created this Xcode project, Xcode and say that you want to support complications, Xcode actually makes a complicationController object with stubs for all of the methods that you need to implement.
And this is pretty handy. We can go through it and just flesh it out and have it give us the information that we need. So I'm going to start at the bottom here. I'm going to add some extra space so we can see.
At this getPlaceholder TemplateForComplication. I want to do this first so that we can actually go ahead and pick the complication and have it look the way that Paul just showed in the slides. So I'm going to remove the boilerplate that Xcode provided. And we are going to make an actual complication template of the ModularLargeStandardBody class.
I'm going to give it an image which is my soccer ball image. Now, so I have got my image as a UI image and I'm creating an image provider using that image and I will not bother with the background color for now.
Let me show you what that image looks like. I have it here in Xcode and I can actually open it up in the Finder. And here is Preview. Let me zoom way in. So as you can see, this is a template image. It's monochrome. It has alpha, an alpha channel so this is the format you want your images to be in so they can get properly colorized.
Okay. So there is our image and we also need to provide some text. So my header text provider is going to say "Match schedule" and my body 1 text provider is going to say "2015 Women's Tournament." I will not provide a body 2 text provider, that's optional for this template. And my goal here is for the text in the first line to wrap to the second line which will happen if you omit the second text provider.
We can build and run this with only that much code added. And then we should be able to pick our complication in editing.
So I will switch over to the simulators.
And let me just double check that the installation happened. Yes, there is our app. That's good.
So I'm going to force press on the simulator, customize, scroll over -- oops! To the complication pane. Actually, you know what I'm going to do first -- as Paul mentioned we only call this placeholder template method once, when your app is first installed, so if you do something like that, you are going to want to uninstall it so that then -- oh, this is -- make sure that it's gone from the -- oops! There we go. So make sure it gets uninstalled.
It's gone. And then we will have it show up again. So that way our code will ask you again for the placeholder template. So let's try this again.
So let's have it not be there and then -- all right. We will add it back in. And let's try this one more time. Yay! Okay .
All right. So we have our template and now we can actually select the complication. So I'm going to go ahead and home out of there, and as you can see the complication, of course, is not showing, switching to showing live data because we haven't written the part that provides the live data. So the -- so it's just going to stick with the placeholder template which is all of the information it's got so far. Let's go back to the code and we will add the part that actually implements the rest of the protocol methods.
Okay. So let me show you about, a little bit about the model that I'm using. I will just switch to the header. I have got a model and written my model in Objective-C. You can mix and match Objective-C and Swift in projects so you can take code written in an existing app and pull it over into your new watchOS 2 app and as long as you include the header for that in your Swift bridging header, it will just work, which is cool. So here is my soccer match model object. And these guys have a date which is the date that the match begins. They have a team description which tells you who is playing, and what group in the tournament this is. And also the model can tell me what the first match in the schedule is, what the last match is and then each match can tell me the previous and the next.
So with that handy, we can start writing the code that's actually going to populate this complication.
So I'm going to write two helper methods to solve the two problems that we need to figure out, basically the two design questions that we need to figure out for this complication. The first one is what should our template actually look like? And for that, I'm going to write a method that takes a soccer match and provides a CLKComplicationTemplate object.
So this is pretty straightforward. We want to build a -- all is well -- build a ModularLarge standard body template like we did for the place holder and get that same soccer ball image provided as an image provider and then we are going to have three lines of text for this one. So the header is going to be the time of the match. And I'm using a CLKTime text provider for that purpose.
And then the first line of text underneath the header will tell us which teams are playing and I'm using a simple text provider with my matches team description, and then finally, the third line is the group description.
So now we have got a template.
We also have to decide how we are going to arrange these templates on our timeline. Now, the naive solution which Paul mentioned in the context of a calendar complication would be to use the match start date to be the date of our timeline entry, but that would have the drawback that it has for the calendar as well which is you wouldn't be able to look at your complication to see what game is about to start. You would only be able to see what game has already started. So we actually want to do the same thing Paul did with the calendar and move all of these entries farther forward. We will have each entry start at the time when the previous match ended. So for that, we have to decide how long a soccer match is, so I have decided that they are about 90 minutes. So I'm going to use a constant that we could change later if we discover we are wrong. So the match duration we will say is 90 minutes.
And then we can write a method timelineEntryDateForMatch that goes and figures out for any given match where that should appear on the timeline. So what we will do is we will say, okay, match, what match is before you? And if there is one, then we are going to use the end of that match as the timeline date for this match.
So if we have one, we will return its date incremented by the duration of a match. And if we don't, that means it's the first match in the schedule and I have pretty arbitrarily decided that I'm going to start displaying the first match six hours before it starts but you could obviously do something different here depending on your use case. So those are the two methods that are kind of doing the meat of the work. Now, we just need to go through and implement all of the different protocol methods that Paul described. So let's start from the beginning. So here we have the timeline configuration and the first method is getSupportedTimeTravel DirectionsForComplication.
The default Xcode template here is saying that your timeline extends both forward and backwards and that's what we want in this case so we will leave that one alone.
The next thing we have to think about is when our timeline starts. So because we have written these helper methods we can do that pretty easily. I will get rid of the handler with nil and instead we will figure out a start date which will be the entry date of the first soccer match.
And let's not forgot to call the handler. And then the next thing we need to do is figure out the end date.
So the end date of the timeline should probably be after the last match is over.
So we will get the last match.
We will get its date, and we will add the match duration to that date and that's going to be our timeline end date. We will call that with a handler. This next method is something we haven't actually gone into at all in the session, but it's an important method if your data is in any way sensitive. So when your user's Watch is locked, you don't want to be showing sensitive data on the screen because that's means they have taken it off their wrist and somebody else could have found it. If you are showing sensitive data in your complication, you can use this method to tell us to be sure to not show that data when the device is locked. Now, the schedule of a fictional soccer tournament is not particularly sensitive so I will leave this at default value of go ahead and show this on the lock screen no problem. So next we get to the timeline population.
These are the really important ones. We need to give the current entry. We need to tell us -- we need to tell the clock how to extend the timeline backwards and we need to tell it how to extend the timeline forwards. I will start with forwards.
So let's skip down to here.
And what we want to do is construct an array of entries starting at the date we were given and going forward into the future from there. My strategy is going to be to make an array, and arrange to call the handler when we are done.
And then we want to iterate through all of the matches until we get one that should start after the date at which point we are going to start creating the templates for that and sticking them in this array.
So I will start with the first match in my tournament. And I have made this an optional, not because the first match might return nil, but because we are going to change this to represent each subsequent match and eventually we will run out of matches it will eventually take on the value of nil at which point we will know to stop. So next we are going to -- oops! While there is a match here, we are going to get the date that we should display that match at. That's the timeline entry date for this particular match.
And now we are going to compare that to the date we were given, which is the date after which we are supposed to be populating timeline entries.
If they are order descending we have gotten into the right section of matches and now we want to start giving back timeline entries for these. So we are going to populate a timeline entry. It's straightforward. We make a template for the match.
That was our other helper method.
We create an entry from the entry date that we computed and that template.
We append it to our array, now we need to be careful not to make too many of these. We have been past a limit.
We want to adhere to the limit. So we will just check now that we have added something to the array, did the count of the array reach the limit, and if so, we will stop. And finally, to make the loop work, we need to go and grab the next match after the match that we just dealt with. So that's going to populate the next N entries in the timeline after the date that we were given, and we can do something really similar to populate the earlier entries. So I'm just going to copy that code that we just wrote. And here I'm going to move up to the getTimelineEntries ForComplication before date method.
Paste it. And we need to make three changes.
Here we are going to do the exact same thing except we are going to start at the last entry and move forward or the last match, rather, and move back towards the first. So we will start with the last match and then down here where the loop happens we grab the previous one. Previous. And finally we only want to start using these when they get before the date we were passed. So we need to switch from order ascending to order descending.
That's our extension methods. The last thing we need to write is the getCurrentTimelineEntry method and we can do something tricky and take advantage of the method we just wrote because getting the current entry is basically getting the entry before a particular date, namely now.
That's the one we want to show currently. So what I'm going to do is call the GetTimelineEntries ForComplication before date method that we just finished writing.
I'm going to pass now as the date.
I'm going to pass a limit of one because we only need one entry here. And then when I get my handler invoked, I'm just going to grab the first entry in that array and pass it back to the handler for the getCurrentTimelineEntry method. So we can go ahead and run this, and what I'm going to do is I'm going to run it and then switch quickly over to the simulator, which as you can see is still running here and still showing us the placeholder template we populated earlier. So as soon as I run this, it will invalidate the timeline on the simulator, at which point it's going to go requery all of these methods again and now that we have implemented them, we will actually get values. So here we go. Run and then swap over. And then we should see our stuff populate.
So there we have actual data. This is showing us - So this is showing us the game that started at 11:00, which is the right behavior if you remember, because we wanted to see the game that started at 11:00 until 12:30, 90 minutes later. If we Time Travel forwards and reach 12:30, we should start seeing this change to the next game. So Time Traveling is working if we get all the way up to 3:00, we should start seeing the one after that. Oops! I went way too far. Our Time Travel is working. All we had to do was basically fill out the three most important methods and we have a functional complication.
So I will pass it back over to Paul who will tell us more about how to arrange for your complication to update as information changes in the world.
PAUL SALZMAN: Thanks Eliza.
So now that we are up and running with our complication. We want to make sure we are always showing something that is accurate to the world around us.
So in watchOS 2 there is a lot of ways you can get contents from the surrounding world into your Watch extension.
You can use the new Watch Connectivity APIs to talk to your companion iOS app and get data onto your Watch. Or use NSURLSession directly to talk to your web services and bring content onto the Watch.
So let's say we have our complication timeline built up and we go out and talk to our web services and get a new piece of data. If that data has invalidated our content, we will need to tell the clock face we want to reload our timeline.
So within our extension, we can get access to the CLKComplicationsServer object.
That object is our interface into the clock face.
We can make a request to the clock face to say please reload my data at which point we are going to throw away all of your existing content and start our communication channels over again by finding out your current timeline and flushing things out from there.
You might already notice that this is a pretty destructive action. If you had a stocks complication, where all of your previous data is still valid.
The clock face isn't querying you right now. It's actually your responsibility to let us know we either need to invalidate or possibly extend your content.
So instead of getting rid of this content you can make a request to extend.
At which point instead of asking you to reload everything, we are going to ask you to append data at the end of the most recent content we have available from you.
So how does this look in your code? You can get access to the complication, the CLKComplicationServer shared instance.
On the shared instance, you can actually query for all of the active complications. An active complication is actually visible right now on your watch face if you were to wrist up.
And given a complication, you can actually make a request to the server to either extend the timeline or alternatively reload the timeline. So that's great. We know it's our responsibility to inform the clock face we need to update, but when do we actually have an opportunity to do this? Well, basically any time you are exception is running you can talk over the CLKComplicationServer to the clock face.
This happens in a couple of instances, like when your watch application is foremost. But you have some opportunities to run in the background via a locally requested wake or even from your iOS companion application using some new Watch Connectivity APIs you can actually wake the extension from the phone so it can receive the data you have sent over.
But because the two last calls allow you to run in the background we have to budget them. If you do a lot of expensive work in the background calls you can exhaust your budget and until your budget is replenished you may not have a chance to update your complication until later in the day. So learn more about the Watch Connectivity APIs please go to the Introducing Watch Connectivity session. There is also cool push functionality we have added in this release to support complication data.
Let's talk a bit more about locally scheduling background wakes in order to get your complication data up to date via one more call on the complicationDataSource protocol. All you are going to supply to us is one date via the handler. And we are going to make this call across all of your complications, not by complication.
When we receive this date, we are going to take this as a hint, and when budgetary constraints or system conditions are good we'll launch you in the background.
At this point your data delegates from Watch Connectivity and NSURL can come in and it's your responsibility to verify if anything has changed and make any requests you need to make to the clock face to update your content.
At this point that's wrapping up our session on complications.
We hope you have learned that to be comfortable with building up a timeline and supplying us with templates and the appropriate providers and to take advantage of all of the hard work that went into the watchOS to actually form and fit your content in these text providers, to be comfortable refreshing your data as the world around changes you, you will get more opportunity to run if you are a good citizen and do less work in your background refreshes.
For more information please refer to your documentation and sample code. We have good technical support and fantastic evangelists. There are great sessions to dig further into WatchKit, thank you! [ Applause ]
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.