Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
>> 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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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!
[ Applause ]
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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 [applause].
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
>> 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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And let's try this
one more time.
Yay! Okay [applause].
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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 -
[ Applause ]
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
[ Applause ]
>> 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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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?
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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 ]