Transcript
[ Music ]
[ Applause ]
>> Hi, my name is Daphne Larose.
I'm a software engineer
on the Foundation Team.
Welcome to my talk.
Thanks to those of you
for sticking around.
I know it's kind
of late in the day.
So, I wanted to start
by thinking about apps
that we commonly
associate with measurements.
The first thing that comes
to mind for me, I don't know
about you, are converter apps.
Makes sense, convert
one unit to another.
But I wonder though,
if there are other apps
But I wonder though,
if there are other apps
that also use measurements but
maybe in a less obvious way.
I don't know, but when I think
about, you know, measurements,
it's interesting, right, because
we use them all the time.
We just don't normally think
about them as explicitly,
and they can pop up in
really surprising ways,
one of those ways being when
they're not in terms of units
that are common for whatever
our current context is.
So, let's say I'm in
France, and I'm using an app
that is calculating
road distance,
and I expect the measurement to
show up in terms of kilometers,
but they show up
in terms of miles.
It's like, hmm.
It makes for kind of a jarring,
not so great user experience.
And so in this talk, we're going
to introduce to you a new suite
of APIs that helps you ensure
that this never happens
in your app.
So, if we think again about
this question of, like,
other apps that we
associate with measurements,
I had mentioned converter
apps, which is still true.
But another example
could be games, right?
So, today we're talking about
this game that I'm working on.
It's called Jammin' in
the Streetz with a z
because z's are cool, and
I want my game to be cool.
So, the premise of the game is
that we have to get our player
to dance from level to level.
I have some pretty set
goals for this game.
One, I need this
game to be super fun.
Like, the most fun a
game has ever been, ever.
Two, tons of emoji,
because I love emoji.
Three, it needs to be
available worldwide.
I want it everywhere.
So, when we think
about some of things
that this game would
include, right,
every round would be
called a jam session,
and within this jam session,
it's tracking the total amount
of time that it takes our player
to get from level to level,
the distance that
they've traveled,
the number of dance
movements they've performed,
and maybe even, like, the rate
at which they're traveling.
But let's think about the things
that I had just mentioned.
But let's think about the things
that I had just mentioned.
Notice anything?
They're all measureable.
They're quantifiable
objects, right?
And so, if we were stop
this presentation right now,
whip out a computer,
and try to code this up,
how would we represent this?
Well, we could represent
them all as doubles.
That's true.
Easy. The problem with that
though is we're actually missing
some context when we do that.
And so let's look at an example.
So we have our little player.
I told you, lots of
emoji, I'm a big fan.
And our little player has
moonwalked, because why not,
and they've moonwalked
a certain distance.
Now, if we stored this distance
as 5, it's like, hmm, okay,
but what does five
actually mean?
It brings up this
question of five what?
But if we say five feet,
it's like oh, okay.
This actually makes sense.
It now has context
in a physical space.
It now has context
in a physical space.
And so now, let's talk
about the way that,
or the API that Foundation
is introducing
to accurately represent
this entire context.
So, you have a new struct.
It's called Measurement, and
it's generic on the UnitType,
and we'll get into what a
UnitType is in a little bit.
Contains a unit as well as
a value, and an initializer.
So, what's cool about this is
that now you're actually able
to represent a measurement
fully with full context.
And so if we go back
to our little player,
let's say in the game we
are not only keeping track
of distance traveled, but
we're also keeping track
of the distance that the
player has left to go.
How would we do this
with this new API?
Well, now we can
represent distance traveled
as a measurement of five feet.
We could also represent
distance to go
as a measurement of,
let's say, six feet.
And so now, you can actually
do some really cool things
And so now, you can actually
do some really cool things
with them.
You can add them together
to get a total distance,
which is a measurement
that represents 11 feet.
You can multiply them
and get, you know,
the value that you expect.
You could also divide them,
and get a similar
value, which is awesome.
So if we go back to
thinking about a unit,
what are some things that we
tend to associate with a unit?
Well, first off, every unit
has a symbol, hands down.
Units can have a dimension, and
so an example of this would be
for the dimension length.
It could use, like, feet
is a unit of length, right?
Units could also be
equivalent to each other,
so one foot could be equivalent
to approximately 0.3 meters.
And so now we have APIs
to actually represent
a unit object.
to actually represent
a unit object.
Now, if you're paying close
attention, before I had said
that every unit has a symbol,
and so in this API we're
representing that here.
We don't necessarily
represent the dimension in here
or the equivalents because not
every unit can be equivalent
to another one, and not
every unit is dimensional.
But at the very least,
every unit has a symbol,
so we're representing that here.
But if we go back
to this concept
of a dimension, what
is a dimension?
What does that actually mean?
Well, dimensions are categories
of units that can be expressed
with different kinds of units.
So, when we think of
length, as an example,
length can be represented
as kilometers,
feet, miles, what have you.
Dimensions also always have
a base unit, so going back
to the length example, the
base unit of length is meter.
You can also perform
conversions within dimensions,
within a single dimension.
So you can convert
kilometers to feet, vice versa,
meters to miles, et cetera.
So now we have API to represent
a dimension, and so you see here
that dimension subclass is Unit,
which means that it inherently
acquires the symbol property.
We have a converter property,
and I'll go into what a unit
converter is in a little bit,
but essentially it defines
the conversion of the unit to
and from its base unit.
We have an initializer,
which takes the symbol
and a converter instance,
and as I've mentioned,
every dimension has a base unit.
So now we have a class property
that will return
that unit for you.
The important thing to
remember here, though,
is that every instance
of a dimension is
in and of itself a unit.
Now, with this new API
that we're introducing,
the cool thing is that
now we are providing
to you some 170-plus units that
you can just use out of the box.
You don't even have to
define them yourself.
You can just use them, like,
after this presentation.
And most of these
units are in accordance
And most of these
units are in accordance
with the International System
of Units, so they're units
that you're already used to
seeing, which is really cool.
Now let's look at an example.
We have the dimension of
length, and it has a whole suite
of units that are class
properties representing
this dimension.
And so, if we go back
to this, you know,
instances of a dimension
are, in fact, units,
what happens is any time you
call these properties you're
getting back an instance
of unit length.
The difference between each
of these instances, that one,
their symbols are different,
and two, the converters
that define their
definition are different.
So, just an overview of all of
the classes that we're providing
to you, all the different
unit types.
Remember I told you there's like
170-plus of them, so, you know,
play with them to
your hearts' content.
But we have some pretty cool
ones here, some common ones,
like area, mass,
temperature, length.
But also, some kind of
uncommon ones like illuminance
and electric current, but sounds
really fun if play with them.
and electric current, but sounds
really fun if play with them.
So, let's go back to
our distance traveled
and our distance
to go measurements.
So, this is code
we've already seen.
Now, we're going to
comment the last two lines
out because we're
going to redefine them.
So, we're leaving
distanceTraveled alone,
but now we're defining
distanceToGo with the same value
of six, but instead of
passing in feet as the unit,
we're passing in kilometers.
Now, this is where things
start to get a little hairy,
because we're now
adding distanceTraveled
and distanceToGo
together, but one's in feet
and one's in kilometers.
What does this mean
for totalDistance?
Is it in terms of feet?
It is in terms of kilometers?
Will this stage just
blow up in five seconds?
I don't know.
But, phew, no, it won't.
Actually what happened is
that the result is in terms
of the base unit,
which is meters.
And so what's cool about this is
that Measurement is handling all
of this implicitly for you.
You don't have to
worry about it at all.
You could just use the
operators as you would
You could just use the
operators as you would
with regular scalar values
and you get the results
that you expect.
So, now let's say, and
again, I wanted to be able
to track the player at certain
points and, for the sake
of the demo we're just printing
out strings, but maybe I want
to do something funkier,
add more functionality
at some point.
But the key here is
that I want to be able
to compare these
measurements, and now you can.
So Measurement has support for
comparison operators for you
to be able to do this.
And so if we were to
friend distanceMarker,
we would get what we expect,
which is barely started,
because Measurement was
properly able to deduce
that distanceTraveled is
less than distanceToGo.
So, let's talk more
about defining a unit
because we haven't really
delved into that yet.
As previously mentioned,
units are always defined
in terms of their base unit.
There are methods that
describe this conversion to
There are methods that
describe this conversion to
and from the base unit
side, but we'll talk
about that a little bit more
when we go into UnitConverter.
The important thing, though,
to remember with this is
that conversion can only happen
within a single dimension.
So, when you think of
length, for example,
you can convert kilometers
to feet, but would you try
to convert kilometers
to seconds?
Conceptually that doesn't
actually make a lot of sense.
And so, in this case, with
Measurement, if you tried
to do that, it would
throw in Objective-C
or it would be a
fatal error in Swift.
So, remember I had
mentioned that we provided
like 170 plus units
for you out of the box.
Chances are, you won't need to
define your own custom units,
but if you do, that's the
only time you have to think
about defining them,
which is pretty cool.
You can actually just use them,
the units that are already
provided for you out of the box,
but if you are creating a
custom unit, then you can think
but if you are creating a
custom unit, then you can think
about how the converter
would be set up.
And so, the cool thing is
that Measurement can
handle the conversion
for you implicitly even
with your custom unit.
It just auto-magically works.
So, let's talk about
some custom units.
We're making some custom
units for the game.
We have a jamz unit here,
because I want to be able
to calculate total time
in a jam session in terms
of this unit called jamz.
Seconds is a little
boring for me.
But if you see here, we have
this UnitConverterLinear object.
What is that?
We haven't really
talked about that yet.
So let's go back to talking
about conversions
a little bit more.
So again, to and
from the base unit.
UnitConverter is a root class
that defines two methods
that describe this conversion.
So baseUnitValue(fromValue),
and value(fromBaseUnitValue).
UnitConverterLinear
overrides those two methods
UnitConverterLinear
overrides those two methods
and defines them linearly.
So, for all the math people
out there, it's in terms
of A X plus B, where
A is the coefficient
and B is the constant.
So, if we go back
to this jamz unit,
we see here that this
coefficient is 30,
a scalar value of
30, so we're saying
that one jamz unit is equivalent
to 30 seconds and so now,
in our linear function, we have
30 times whatever the jamz value
would be.
So, if a jam session was
four total jamz, four jamz,
then if we wanted to
convert it to seconds,
it would be four times 30
to give us 120 seconds,
and you could do the reverse.
120 divided by 30 to
get the jamz value.
Yeah. So let's say I wanted
to define other custom units,
for length, for example,
because I don't want
to calculate distance traveled
in terms of feet or meters,
I wanted to do something a
little bit more interesting,
like hopz.
So, here, same concept.
One hopz is equivalent
to 0.75 meters
One hopz is equivalent
to 0.75 meters
and so we're defining the
formulas for that here,
and same with some
other custom units
that I would create for this.
So, you start to get the idea.
So, if you recall
correctly, when I was talking
about the jam session,
one of the measurements
that I had named was the number
of dance movements performed.
Unfortunately, the
international system
of units does not
have a dimension
that recognizes dance movements.
I'm not entirely sure
why, but it's a thing
that we are defining here today.
And so, we have UnitDanceMove
with a base unit of
wackyArmMovements.
And so you see here that one
wackyArmMovement is equivalent
to another wackyArmMovement,
so what this means,
another way to think about how
you define your units is how
many of the base unit make
up this particular unit.
So, here's one-to-ones so
our coefficient is one.
Let's say we wanted to
define a robot movement.
It's like, approximately
like four wackyArmMovements,
I think [laughter].
like four wackyArmMovements,
I think [laughter].
cabbagePatch is like
three wackyArmMovements.
Sure. And of course, no dance
movement dimension is complete
without jazzHands, which is
about two wackyArmMovements.
I think it's pretty accurate,
we'll stick with that for now.
And so now, let's go back and
actually create this jam session
that I had outlined earlier.
So distance traveled, this
is in terms of steps taken,
which will be in terms of this
unit hopz that we created.
jamTime will be in
jamz, naturally,
and dance moves will be
in terms of the robot.
So our player will be roboting
the entire time, of course.
And so the dance rate
will actually be terms
of meters per second,
but if you recall,
none of the other measurements
were in meters or in seconds,
so how are we going
to derive that value?
Well, we can take stepsTaken
and convert it to meters.
We can also take the jamTime
and convert them to seconds,
and now we can actually
return a measurement that's
and now we can actually
return a measurement that's
in terms of meters per second.
And so the cool thing here
is that in very few lines
of code we were actually able
to completely define
our jamSession.
So, now we know how to actually
represent measurements and units
as model objects,
which is pretty cool.
But I told you, though,
that I want this game
available everywhere.
Now to do that we actually have
to format these measurements.
That's where things
get a little tricky.
So, if we have our player, and
let's say instead of dancing
for only five feet, they danced
the robot for five kilometers.
Hard core.
If we wanted to represent this
across the world,
how would this look?
Well, in Canada, we could
actually just write it
as it was previously written,
which would be five kilometers.
If we were to try to represent
this in Chinese, however,
we'd actually have to
translate the unit.
In Arabic, we'd have
to translate the unit
and change the number
representation and make sure
and change the number
representation and make sure
that our right-to-left
ordering is correct.
So, all of this is more
logic that I'd have
to add manually to my app.
And then finally, in the
U.S., we're like, "Kilometers?
What are those?
What are those things?"
So, then, not only do I have to
handle conversion, just in terms
of my calculations, but I also
have to handle conversions just
for formatting, which is
additional logic I have
to add to my app.
So, what's the solution here?
You let Foundation do
all the work for you.
We have a new formatter
called MeasurementFormatter.
It formatters both measurements
and units, and its locale-aware,
so you don't have to
worry about any of this.
So let's take a look at it.
Its subclass is Formatter.
If you're familiar with any
of our other formatters,
same concept.
It has the unitOptions property,
and we'll talk a little bit more
about unitOptions in a second.
It also has unitStyle.
So, again, if you're familiar
with the other formatter,
short, medium, long.
It has a locale that's setable.
Now, chances are, you're
just going to default
to the current locale
of the user,
which is what this locale
will always default to,
but if you needed to set
it explicitly, you can.
It also takes a custom
numberFormatter.
So, let's say you wanted your
value in your measurement
to be represented in terms
of scientific notation,
you can provide a
custom numberFormatter
to do that for you.
It also has methods that take
in both a Measurement
object and a Unit object.
So let's talk a little bit
more about the unit options.
The cool thing is
that out-of-the-box,
by default the formatter formats
according to the preferred unit
of your user's locale.
So, you don't even
have to think about it.
It also takes into account
things like purpose.
So, you know, if you're
calculating a length in terms
of road distance verses in terms
of person height, you're going
to want to use different units
depending on the context.
to want to use different units
depending on the context.
So I'll take a look
at some of the options
that MeasurementFormatter
provides.
One is provided unit, and
so, let's say we have a case
where we want to pass in a
measurement of five kilometers,
but our locale is the U.S. Now
normally in the U.S., we would,
for road distance for
example, change it to miles
because that's what's
common for us here.
But if you set provided
unit, it'll ensure
that whatever unit you
pass in is the unit
that actually gets formatted.
There's also an action
called natural scale.
So, this is great in
particular for, like, UI stuff.
So, if your, you know, your
app is running on the watch,
and you're really concerned
about screen real estate,
then instead of putting
in a thousand meters,
which would take up a
bulk of your screen,
you can actually have it
formatted to one kilometer.
Another is temperature
without unit.
So let's say you
have a measurement
that represents 90
degrees Fahrenheit,
but you don't want
the Fahrenheit unit
to actually show.
You can set this to get the
result that you were expecting.
You can set this to get the
result that you were expecting.
So, let's play around
with some examples.
We have our formatter here,
and we have our original
distance measurement that's
in five kilometers,
because that's how far our
player danced.
And now we want to get a
resulting string from that.
You'll see that the result is
in miles and the coolest thing
about this is that in
three lines of code,
not only did we get the
result that we expected,
but we didn't have to do
anything to the formatter,
just out-of-the-box
without setting anything,
it knew exactly what to do.
Now let's say that we give it
our custom unit, the hopz unit.
And Measurement,
MeasurementFormatter
that has no conception, no idea
that hopz is actually a unit,
but we create this hopz
distance, and we pass it
to the formatter, and
it's actually still able
to do the conversion
implicitly on our behalf.
We don't have to do anything.
Now let's say though, you know,
we have these custom units
Now let's say though, you know,
we have these custom units
and we actually want
people to see them,
so we'll set provided unit.
We'll give it a measurement
that has our hopz unit again.
And now the result will
be in terms of that unit.
Now this case is a
particularly interesting case
because not only are we
providing a custom unit,
but we're also providing
a custom unit that's
within a custom dimension,
right?
And so at this point, it's
like, not really sure,
what will MeasurementFormatter
do?
Oh, well, it'll do
exactly what we expect,
which is pretty awesome.
So, now, I'm going to hand
it over to another member
of the Foundation
team, and he's going
to show how measurements
are used and can be used
and formatted in the High
Scores feature of this game.
Thanks [applause].
>> Thank you, Daphne.
So, my name is Peter Hosey.
I am also an engineer
on the Foundation team,
and like Daphne said, this
is our High Score list,
where we're showing a list
of the levels in the game.
As you tap on each one,
you see some basic facts
about the level, the
name, a picture of it.
You see some important
information
about playing the level,
things you need to know.
And you even see your statistics
of how well you've
done in the game.
You see things like
your high score,
how many wacky arm
movements you've done.
You see how far you've danced.
You see how fast you've danced.
But these values are
all just numbers.
They lack dimension.
So we don't know,
like, how far is 6811?
Now you could implement an
entire unit system yourself.
You could start small by just
tacking a unit onto the end
of each of these numbers.
You could maybe build out
a little more, and like,
have a unit conversion system
that will understand
different locales
that will understand
different locales
and translate the unit
names automatically,
and that's a lot
of work, isn't it?
Like Daphne said, let Foundation
do the work do the work for you.
We now have Measurement
and Unit,
and MeasurementFormatter
types in Foundation,
so let's use them in our game.
So we're going to start by
creating the custom units
that Daphne showed you,
some of them anyway.
We've got a couple of length
units, we've got our speed unit,
and we have our four
custom dance movements
that do not come
with Foundation.
Now that we've got our custom
units, we can bring these
into our model, which
is this levels struct,
which has our basic
facts about the level
and includes the
player's statistics,
which are just numbers.
So let's change them
to measurements.
So, now we have a measurement
of dance moves, a measurement
of length, and a
measurement of speed,
and as we change the properties,
so much we change
the initializer.
so much we change
the initializer.
So, now it's possible to create
a level with measurements,
and we want to do that in
the List ViewController,
which is a controller for this
view here, this is the list.
Our List ViewController
queries are synchronization API,
which returns a JSON list of
dictionaries, one per level,
containing all of
this information.
And particularly includes
the player's statistics
as just numbers.
So, we want to create
measurements
around these numbers,
so now this a number
of wackyArmMovements.
This is a number of Hopz.
This is a number of hopzPerJamz.
Now that this information is in
our model and we've created it
as measurements in our
List ViewController,
and that's the only
change that you had to make
in the List ViewController.
Now we can go over to the
Detail ViewController,
which is what shows this
view here, and shows this
for every one of these levels,
and we can use our
new measurements here.
So, we already have
one formatter in place.
This is a NumberFormatter, and
this is what does this padding
to six digits with zeros,
and we want to keep that,
but we want to build
on top of it.
We want to make this
show the units.
So we're going to add
two more formatters,
and I'll explain
why to in a moment.
Now that we've created them,
we go to the same place we're
already configuring the number
formatter, which is in
our viewedDidLoad method.
This is inherited
from UIViewController,
and here we're overwriting it.
We configure the
highScore NumberFormatter
with our minimum integer
digits, and now we're going
to configure our
MeasurementFormatter
to use our provided unit,
which is wackyArmMovements,
which you can't tell
here, can you?
And use our NumberFormatter
so that we continue
to pad to six digits.
And I mentioned we created
two new MeasurementFormatters,
And I mentioned we created
two new MeasurementFormatters,
so let's configure
the other one.
This one we want to use the
StandardNumberFormatting,
so we're not going to
set this NumberFormatter,
but we're still going to set
it to use the provided unit.
And now if I run that, oops, oh.
So, I've configured
the formatters.
Now I need to actually use the
strings that they'll give me.
So we're already
talking to one formatter,
and we say sting
from this value.
Well that worked fine
when this was a number,
but now it's a measurement.
So now we need to talk to
the MeasurementFormatter.
Now, it's a simple
one-word change.
Otherwise, it's exactly
the same.
Works the same for
any formatter.
The other two we're creating
string directly to the number,
and you can imagine how
this is not very good
for your localization effort.
Here too we want
to use a formatter.
So, we'll use our other
formatter, and it's,
again, the same thing.
We do string from, in this
case a string from Measurement,
We do string from, in this
case a string from Measurement,
and this returns a string,
and we pass that to our label.
So now we can run the app.
And now we see our
custom units show up.
So this is a start, but,
we're still not really
providing real world context,
which is our goal to start with.
We need to show these
in real world units.
So we could do some
conversion logic of our own,
but MeasurementFormatter
can do that for us.
So, we're going to create one
more MeasurementFormatter,
and as we have the custom
units, MeasurementFormatter,
we're also going to
have the Locale-Aware
MeasurementFormatter.
As we create it, same
as we configure it,
except we don't' actually
need to do anything,
because MeasurementFormatter
out-of-the-box converts
automatically to the unit
that the player expects
for their locale.
that the player expects
for their locale.
Now, this is where it's going
to be a little bit tricky,
so bear with me a moment.
We're currently talking
to one formatter,
asking it for one string,
and passing it directly
to the label.
So what we're going to do
instead, is we're going to talk
to the customUnitsMeasurement
Formatter first,
get its string for
this distance.
Then we talk to the
localeAwareMeasurement Formatter
and get its string
for this distance.
And then we'll use a
Swift string interpolation
to put these two things
together and generate one string
that we will pass to the label,
and we did this for the distance
and we do it also
for the dance rate.
And that's all we have to do.
In order to show both our
custom units and the units
that the player expects
for their locale,
their real world distance.
[ Applause ]
But we don't need to stop there,
because remember the goal
here is to have this game all
over the world in every country.
So miles is great in
the United States,
we're in the United States.
We see miles.
But we want to make sure
this works in every country.
So I'm going to make use of
an Xcode feature that's part
of your scheme, so I'm going
to edit my scheme here.
I'm going to duplicate
the scheme,
and when it duplicates
the scheme.
It's going to ask me for a
name for it, so I'm going
to give my scheme a
very distinctive name.
And now, having named my scheme,
I'm going to make one simple
change, under the run verb,
I'm going to make one simple
change, under the run verb,
Options tab, Application Region.
I'm going to our testing into an
exotic locale, like, say Canada.
And now, I'm going to
run with this scheme,
and with no code
change to the app,
with no configuration
changes in the simulator,
just changing the scheme, we
can now see that in Canada,
this shows kilometers.
That's all you have to
do to make this work
with our new Measurement
and Unit
and MeasurementFormatter
types in Foundation.
Thank you [applause].
Thank you, Daphne.
>> Thanks so much, Peter.
So, obviously this game
is going to be a huge hit.
I'm, like, super
pumped about it.
Let's wrap up real quick though.
So, we just saw throughout
this whole talk
and in the demo how Measurements
and Units are now model objects
that we can use in our apps,
which is really awesome.
that we can use in our apps,
which is really awesome.
We also saw that it's
super easy to format them
and they require very little
work on our part, which is cool.
And the best part
is that we get all
of this very powerful
localization for free,
without having to specify
any objects, or any options,
just right out of the box.
So, now you don't actually have
to suddenly become a
polyglot or, you know,
change all the logic
and coding in your app
to be able to support this.
You could just use it as-is.
If you'd like more information,
you should check out the link.
These sessions are
actually already passed,
but if you're super
interested in it,
I would recommend
checking out the videos,
and thank you so much.
[ Applause ]