Transcript
[ Crowd Sounds ]
>> Good morning, everyone.
And welcome to session 237 this
early Friday morning.
My name is Fredrik Olsson and
I'm an engineer on the MapKit
team.
And today I'm here to talk about
what's new in MapKit.
The generic map, the standard
map that has been with us for a
couple of iterations all the
time, is a good map.
It has a road network for easy
navigation.
You have your points of interest
for reference.
But sometimes your application
has a little bit more specific
kind of data that you want to
stand out.
Like we had in AppleMaps when we
added the transit mode when we
want the transit line to stand
up and the map to take a little
bit of a step back, mute down
and let the data pop so the user
can see what is important at the
moment.
In new iOS 11, we give you muted
standard map type but do the
same for your applications so
that your data can stand out on
your map and your applications.
The map is not only the map.
There are also a couple of
system controls that we provide
for free on top of the map.
You have up in the left corner
here you can see the scale view.
You have the complex view up in
the right corner.
And down in the bottom right you
have a friend that has been
available as API for your use,
the user tracking bar bottom
item.
Now, it is a bar bottom item
which means that you can only
use it within the context of a
bar, such as navigation bars or
tool bars.
And if you want to display it
anywhere else, you have to
unfortunately roll your own.
And in iOS 11 we fixed that and
we give you a proper user
tracking button that is the
UIView subclass so that you can
use it wherever and whenever it,
well is good for the context of
your application.
When we give you the user
tracking button, we thought why
not give you more?
So, yes we will now also give
you the compass button.
The compass button is by
standard app in the upper right
corner.
By having it as a UIView button
you can position it wherever you
want.
You can even put it in one of
the navigation bars if you want
to.
The compass button has one more
property than the user tracking
button do.
It's the compass visibility.
It's on purpose that the compass
visibility is not just
repurposing the hidden property
or some equal simple bool yes
show it or don't show it.
We already could show the
compass on the map view by using
the shows compass on the map
view itself.
Which is a Boolean yes no.
But it's a bit limited and
doesn't encompass all the
features that we want to expose.
So, feature visibility is a new
enum.
It has three states.
The adaptive mode which is the
default, hidden and visible.
And if you look at them, hidden
is obvious.
It does what it says.
Visible also does what it says,
so no reason to talk much more
about it.
But the adaptive default mode do
require a little bit of
explanation.
What the adaptive mode do is
that if the user has rotated the
map a little bit away from pure
north, we display it.
And if the user rotates back to
pure north, we fade it away.
And being a button, if you tap
on it we will rotate back to
pure north and fade it away as
well.
It is tapable in all the
different visibility states, so
you can always tap it.
It will always be a button.
But it's a nice catch.
And if the user rotates away
from pure north past the little
threshold, we fade it in again.
And that is the compass button.
The last sibling, the scale
view.
Also have the visibility
property which is the same three
states in enumeration.
Hidden, self-explanatory.
Visible, also self-explanatory.
And adaptive is a new behavior
where when your user is zooming
in and out, then we display the
scale view.
Otherwise, we hide it.
The scale view has also been
available on map view previously
with the show scale property on
map view.
The default behavior for show
scale and show compass has
unfortunately been a little bit
different.
So, where the default for
showing the compass has been the
adaptive mode where we hide it,
the default for scale has been
to always show it.
And now with the scale view
being exposed to you, you have
all three options available to
use as it fits in your
applications.
There is one more property on
the scale view.
The legend alignment.
The legend alignment is related
to the also new and improved
localization support for map
view.
Previously, the compass has
always been in the upper right.
The scale has always been in the
upper left.
These are the defaults you get.
But new in iOS 11, if you
recompile against iOS 11 SDK, we
will instead fix the compass
against the trailing edge and
the scale view against the
leading edge to get that natural
look and feel for our
right-to-left users.
This, however, has one little
complication that leads us into
legend alignment property and
why we need it.
The default is leading, which
means that we will fit the
serial point of the legend to
the edge, the leading edge.
And the upper bound of the
legend is flexible and will move
as the users zoom in and out.
And the leading edge for in this
example British English would be
the left-hand side.
And for Arabic in this example
is always the right-hand side.
If you instead want to align
your scale view to the opposite
side of the screen, you need to
set the legend alignment
property to trailing and we will
flip the zero point that is
fixed and to dynamically adapt
to the localization that is
currently being used.
This makes a little bit more
sense if we look at it as the
user actually interacts with the
map.
The zero point, the zero points
are fixed but as the user starts
zooming and this animates, the
upper edge is moving.
And if that upper edge is the
closer edge to the screen, it
looks kind of silly as the zero
point in the middle of the
screen is fixed but the other
one is moving.
So, you need to change it if you
align the scale view to another
page.
In using this simple APIs, the
muted standard map and exposed
map controls, we can take this
fictional bike sharing
application that we look at
today.
It is an application that allows
us to share unicycles and
tricycles in the San Francisco
area.
We can take this application
that is a little bit dated and
making it slightly more modern.
Now, let's look at that one more
time.
It was a little bit small of a
difference maybe.
We can take the slightly dated
application and bring it to the
future.
As we see here, the big
difference might not necessarily
have been the map mode and the
controls.
The important part on your maps
in your application is your
data, your annotations,
overlays, etc. And this is what
we are going to focus on next.
The standard way to display our
annotations has been the pin
annotation view since I was two
days.
The pin annotation view comes in
two different states.
We have the normal state when we
show it on a map and it's a pin.
And the user can tap on it or we
can programmatically select it
and it becomes the selected
state with a callout and show
the title and subtitle.
There is a little drawback here
and that is we only display the
title and subtitle if the pin is
selected.
So, as a user, you have to tap
on everything to see what they
actually are, which is a little
bit cumbersome.
So, as you saw in Maps app from
iOS 10, we use something that
looks a little bit like this
instead.
And with iOS 11, it's available
also to you.
We call it the
MarkerAnnotationView.
It is a AnnotationView subclass,
so it's a sibling to the pin
annotation view.
And we think you're going to
like it so much that it's the
new default for iOS and tvOS.
So, if you don't implement the
delegate callbacks for creating
them yourself, we will give you
a MarkerAnnotationView as
default if you recompile against
iOS 11.
The MarkerAnnotationView also
have two states.
We have the normal state which
will try to display the title as
well below the marker if there
is room on the map.
We have the selected state.
And in the selected state, we
also show the subtitle if there
is room for it.
And that way the user will at
the glance at the map, see what
are all of these data points
without having to tap on them
one at a time.
Now, there is one more thing.
We have to go back to the pin
one more time.
If we look at this pin and the
callout, there is one small
little thing in the callout.
You can add accessory views to
them.
You can add detail views to
them.
You can configure them.
And MarkerAnnotationView is an
annotation view.
So, all the API from annotation
views are supported for
MarkerAnnotationViews as well.
And if you add a in this case a
right accessory view to the
callout, then there is no
natural location within the big
marker where you can display
that data.
And it's even more obvious if we
think about the detail view
where you can put endless amount
of data, a lot of data, a full
placecard of your item if you
want.
So, what do we do in that case?
We have to use a third state of
the MarkerAnnotationView.
So, in practice, your
MarkerAnnotationViews have three
states.
We have the normal state when
it's unselected.
We have the selected state.
And the selected with callout
state which we will use if you
configure the marker annotation
with views that cannot be
displayed in the marker alone.
And with that, let's look at the
code.
This is the
MarkerAnnotationView.
The MarkerAnnotationView first
and foremost you have the titles
and you can configure the titles
that are displayed below it.
And notice that we yet again see
this three-state enumeration,
the feature visibility.
Because you can configure it in
many different ways, not only
visible and hidden.
The hidden obvious.
We don't show it.
The visible is also obvious.
We will always try to display
the title and the subtitle if
you configure them to be
visible.
Do note, I say we will try.
If the map is dense with data,
we will opt to hide the titles
to unclutter the map and display
more information as needed.
So, we will display them most of
the time but they can be hidden.
And in the last visibility we
have is the adaptive mode.
The adaptive mode is also the
default mode which gives you
that for free option for the
subtitle, especially where we
will hide it when it's in normal
state and display it when you're
in selected state.
So, that is the title below the
marker.
Next up we have the markers
themselves that you can also
configure.
First with the color.
So, there's the marker tint
color and the glyph tint color.
The marker color is the color of
the marker itself.
It is a normal UI color.
The default value is a nil value
which will pick a system
default, which is currently a
slightly reddish color.
You can set it to any other
color you want, for example why
not orange.
Do note that we call it marker
tint color, not just color.
And that is because the color
you provide might not be the
color, the exact color that we
use in the end.
We may apply some visual effects
on the color to make it look
nicer.
So, it's a tint that you're
applying.
And you can even use the new in
iOS asset catalog name colors of
course.
And the same thing for the glyph
tint color which is instead the
color of the glyph within the
marker which is defaulting to
white pin in this case.
Speaking of the glyph.
You want to configure also what
is inside of the marker, the
little glyph in it.
And the easiest way to do it is
by using the glyph text
property.
It's a simple string.
We see a pattern here.
It defaults to nil.
And when it defaults to nil, we
pick a system default which is a
pin.
It's a pin in a balloon.
It might be dangerous, but it
works.
And you can set it to any text
you want.
You might want to grade your
annotations.
You can even use the characters
or the Unicode standard to get
vector graphics for free.
Now, glyph text is a string.
So, you can set it to any text
you want.
If you want, you can put the old
iliad into it and we will try to
render it.
But it will look silly.
We will try to shrink down the
text as much as possible but the
user will not really be able to
read what is on screen.
So, as a rule of thumb, please
try to limit yourself to around
two to three characters.
And not everything though his
representable by characters or
text.
Not even with the full Unicode
standard being expanded every
year.
So, you might want to use your
own image graphics.
And for that we have glyph image
and the selected glyph image
properties.
The glyph image property is the
only property that you probably
will want to use most of the
time.
And you just give it an image
that is 40 x 40 points.
And when, which is used in the
selected state.
And then for the normal state,
we will shrink it down to 20 x
20.
Which works most of the time,
unless the graphics you have is
quite detailed, since then you
can get graphical scaling
artifacts as we scale it down.
And if that is the case, you
need to use both of the
properties.
So, for the small glyph image,
you can provide the smaller 20 x
20.
And for the larger one you give
it the bigger 40 x 40.
We will cross fade between the
two and you get that crispiness,
both in the normal and selective
states.
And using that, we can get this
much, much more modern-looking
application.
Now, this application though is,
if I have to say so, a little
bit cluttered.
You might want to clean it up
and have it easier to look at
and find what you actually want.
You want it to look a little bit
more like this.
And there is one single property
that we add in iOS 11 to do
this.
We call it the display priority
where we can tell annotation
views what priority are you in
order to display on the screen.
The feature display priority is
modeled after the layout
constraint priorities from UIKit
and MapKit.
Which means it's a floating
point range between a 0 and 1000
where 1000 is the required
value.
And if we look at what they do,
required means opt out.
Do not occlude or hide anything
as the annotation views overlap.
Do nothing.
But if you set the display
priority property to anything
else, like the provider
defaults.
Default high which is 750 and
default low 250, yes, that's the
layout constraints.
Then, you get these annotations
hiding.
And you can get from the, oops.
That is not what I meant.
So, being a floating point value
from 0 to 1000 means that you
can use it for nice things.
If you for example map your
display priority to the
popularity of your items.
So, you have a high, a very
popular item get the high
display priority and a less
popular item get a lower display
priority, that would mean as the
user is zoomed out on a high
level and looking at the whole
city, the most popular items are
the one that is remaining on
screen and visible.
And as the user zooms in, you
see also all the other data.
Which allows you to go from this
very cluttered map to something
that is a lot cleaner.
Now, if you do the popularity
thing and hide all the data,
then yes all the data is hidden.
So, the user will have to zoom
in a bit to actually find it and
then zoom out again to see the
rest of it.
And there is a better way to do
it.
What we could do is just cluster
the annotations and say that
yes, here we have 11 more and
here is 8 more.
You don't have to zoom in.
Well show you.
And the API for this is just as
simple as the API for display
priority.
We call it a clustering
identifier.
It is a string, so you can set
it to anything you want.
It is modeled after the reuse
identifier.
Both annotations views, your
table view cells and collection
view cells.
So, that you can have groups.
In the case of our application,
the bike sharing application, it
might be that we want to cluster
unicycles with unicycles and
tricycles with tricycles, but we
don't want to mix them together.
And if you can achieve that but
give them those annotation views
different but distinct
clustering identifiers.
So, as you use it, the default
tis nil.
If you set it to nil, you opt
out and nothing happens.
And if you set it to any other
string, all annotations sharing
the same annotation view are
illegible for being clustered
into a group as they overlap.
This is a very powerful and
beautiful feature but a little
bit hard to explain.
So, let me switch over to the
demo machine and show it to you
instead.
There is the demo machine.
So, in this little sample app, I
have four annotations.
I have a unicycle and a second
unicycle that share one
clustering identifier.
And I have another tricycle and
a tricycle sharing a second
identifier.
If I move them together, we'll
see their cluster.
And if I drag them apart, they
decluster again.
Now, all four of these have the
same display priority.
So, all four have the same
display priority.
When I group them together, the
cluster will get the display
priority of the highest display
priority within the group.
So, in practice, this means that
in this example, both of them
still have the same display
priority.
And when I move them even
further together, they collide
and they hide each other.
Move them out again and they
hide each other.
Now, if two annotation views
have the same display priority,
which one should we hide?
And there is logic to it.
If we rotate the map a little
bit and move them together, we
see we are still hiding the
green one.
If we rotate 180 degrees and
move them together, we notice
the orange one is the one
getting hidden.
So, the logic here is if they
have the same display priority,
we hide the one that is closest
to the top of the screen.
Which is good for developers and
users because you can see,
expect what is going to happen
next.
But one of the many reasons this
was done, I have to say if we
tilt the map into 3D mode, it
looks kind of cool as you rotate
in the annotation behind the
other one.
Now, let's zoom out a bit and
drop another annotation on the
map.
Being in annotation view, you
inherit all the APIs from
annotation view including
dragging.
So, I can pick it up and drop it
down again.
I can pick it up, drag it around
and drop it down again.
[ Applause ]
Did you notice something
happening there as I dropped it
down close to beach street?
So, look closely as I pick it up
and drop it down on Dollar
Rent-A-Car.
It gets hidden.
If you set the display priority
on annotation views, you will
also, we will also try to
collide out items and labels on
the base map for you to keep the
map even more uncluttered.
So, I can pick it up and why not
drag it down, drop it down on
another annotation and they
merge in one smooth animation.
All of these are actually
draggable.
So, I can pick them up and pick
them up, which is not true.
Because the cluster itself is
not draggable.
In order to be a draggable
annotation, you need to have a
coordinate.
And a cluster doesn't have a
coordinate on its own.
The geographical coordinate for
the cluster is the average of
all the member annotations that
is currently encompassing.
And that is why you can't drag
it.
But back to the slides.
It's a simple and powerful API
that allows you to go from this
cluttered mess to something a
little bit more clutter
cleanliness.
And this is good, since now we
see there are 11 annotations up
there and we have two more up
there.
And, but we don't really see
what kind of annotations are
there.
So, being unicycles and
tricycles, I really don't want
to go to a location where there
is only unicycles.
Because I haven't bothered to
learn to ride one yet so I want
to make sure I go to a place
with tricycles only.
So, we want to have the clusters
display at the glance what kind
of data do they contain at this
location.
And that we can do by doing our
own custom annotation views.
So, we can display it as a
little pie chart.
And I can see, yeah, there are
at least two tricycles there.
It's safe, I can go there.
How do we do this?
We have to go to our good old
friend the map view for
annotation delegate callback.
This delegate callbacks has
always provided a system
annotation for you.
The user location annotation
which is the representation of
where is the user currently in
the world.
And you can either return nil
value and you get the default
pack.
Or you could return your own
custom annotation and display
the user location in a
completely different way that
fits your application needs.
And for everything else you DQ
create your annotation view and
return it.
If you return nil, you get the
system default which, in iOS 11,
is the MarkerAnnotationView.
Now, in iOS 11, you will also
get one more different kind of
annotation, system annotation in
your callbacks.
You might get this
ClusterAnnotation.
So, if you opt into clustering
and they collide, there is not a
new delegate callback.
We reuse the existing callback,
give you a ClusterAnnotation
that represents the cluster, and
you can return the
AnnotationView that you want.
The ClusterAnnotation is a
simple concrete class of the
annotation protocol.
It provides you an array of all
the member annotations.
It's a flat array, so we
guarantee that there will be no
clusters within clusters within
clusters.
It's a flat array of all the
annotations that is currently
representing and only those.
We order it so the one with the
highest display priority is
first.
So, if you, as I said
previously, using display
priority and mapping it with
popularity, you will get the
most popular one at the start.
The MarkerAnnotationView placed
well with the ClusterAnnotation.
So, we will display the count
there as the glyph.
So, you can see at a glance how
many there are.
We will also, for the title and
subtitle, provide good default.
So, the title will be the title
of the most popular one or the
first member annotation.
And the subtitle is a localized
plus X number or more how many
annotations you want to have.
Both of these properties are
mutable, so you can change them
anytime you want and provide
something else.
We might want to change it and
have, say, three unicycles and
two tricycles and then you can
see at the subtitle what it is
without doing a custom
AnnotationView.
But you might want to do that
custom AnnotationView.
So, custom AnnotationViews.
Let's look at this little pie
chart.
A AnnotationVview is a view.
A view has a frame.
And the underlying thing that
drives both display priority to
hiding things and clustering is
that annotations must be able to
detect if they are overlapping.
And since we are views and we
have a frame, we can just use
that frame and detect a
collision.
Now this doesn't look good.
I mean, they're circles, yes.
The frames are colliding but
visually they are not really
colliding yet.
So, this is not quite what we
want.
So, we add one more property on
AnnotationView.
We call it collisionMode.
The default value is rectangle.
So, we have to use the frame.
The other option we have is
circle.
Where we use the inscribed
circle of the frame for the
annotation.
And now when we move them
together, they collide and
detect a collision when they are
visually colliding.
Now, not all AnnotationViews are
perfect boxes.
Some of them are actually
rectangles.
And for collisionMode rectangle,
yes, the collision is with the
rectangle.
But if you change it to the
circle, it is a circle.
So, we use the inscribed circle
in the rectangle, not the
inscribed ellipses.
It's important to take a little
bit of note there.
So, not everything is rectangles
or circles either.
We might have something a little
bit more fancy, like
nice-looking 3D rendered golf
clubs for our golf application
that is displayed in flyover.
And as we tilt the map in 3D and
we move them together, we want
them to actually overlap a
little bit because it looks
cool.
It looks really nice, actually.
So, if we use just the
rectangles, they are way past
when we want them to detect
collision.
If we use the circles, no.
it doesn't really fit either.
I mean, what we really want is
something a little bit more
close to this which would be the
circle in the bottom with the
five-point outset option that we
did not give you [laughter].
You don't have that option,
sorry.
Instead, we went back to all the
way when outer layout was
introduced to both iOS and macOS
in the year before.
And you have the alignment
rectangle insets which are the
options that say that you can
configure if two views re to be
aligned next to each other, what
kind of insets would they use to
look visually pleasing?
Which is just what we're doing
here with the AnnotationViews,
right?
What is the visually pleasing
collision rectangle.
So, all you need to do.
Override it and you provide
offset.
So, for the top we can set it to
50 points and we move the
collision area down.
The left edge, 0, we don't need
to change it at all.
The bottom edge we move out by
negative 5, because we want an
outset, not an inset.
And then we do the right edge as
well.
And what we have is that little
circle in the bottom left outset
by 5 points, just as we wanted.
And when we now collide them, it
looks very visually pleasing.
Let's look at the code to
configure our custom
AnnotationViews.
And a few tips and tricks to
make our code base a little bit
more modern and nice.
There we go.
So, let's run the application
first so we see that it works.
Build succeeded.
Compiling.
It is running.
And we have the collision when
we move them together.
They collide out.
So, it works.
Now, looking at the code, we
have our mapView, viewFor
annotation.
The first thing we do is we
check if that is one of our own
annotations.
And if it is, we configure it a
lot to set it up.
And otherwise we check if it's
the system-provided
ClusterAnnotation.
And if it is, we configure it
differently.
And for the else case, we just
return nil to get the system
default.
Currently that would be called
for all the user locations, but
it might be that we add more
system annotations in the
future.
So, good take.
Don't check for user location by
default.
Do the else case, return nil,
and let the system do the right
thing for all the other cases.
Now, this is a lot of code, in
our view controller nonetheless,
and it doesn't really have
anything to do with how it's
going to be displayed or rather
it is all how it's going to be
displayed.
So, I don't think this should be
in the view controller.
So, if I grab here and drag in a
couple of views to my project, I
think that first of all I know I
need to add these files to the
target or it won't compile.
There we go.
Another good tip is in your
AnnotationViews, you can
override the set annotation
method and do the configuration
there.
So, here I have the same thing.
I know I will get a bike.
And if I get a bike, I configure
it, set it up, and be done with
it.
The, this will allow me to go
back here and delete all of
this.
No, actually, not that one.
And delete all of this, because
it's really not fitting there in
the view controller.
It gets a lot, lot smaller.
Another thing that might annoy
SWF developers is that this view
here is an optional.
We really don't want optionals.
So, we want it to be normal
required variable.
Now, the DQ reusable annotation
with view do return an optional.
But we fixed that and give you a
sibling.
The DQ reusable annotation with
identifier for annotation.
It is modeled after the DQ
methods on UITableView and
UICollectionView.
So, it will always return an
instance.
Let's give it a, oops.
Yeah. That's how you type.
And the annotation.
You might notice I did not have
US keyboard here.
And by doing that, we no longer
have to do the if check for did
we actually do it and create it
instead.
We don't have to set the
annotation, because we give it
to the initializer.
And we can just delete this.
Actually, let's go one further.
We can delete all of this.
We don't need that.
Because we can register our
AnnotationViews to use for reuse
identifiers.
So, just as with the TableViews
and CollectionViews, we have
tell the map view here, I want
to register.
And use the bike view class for
any view with the reuse
identifier that could be of your
choosing or one of the two
constants that we provide for
you.
The default annotation view
which we use for any single
annotation view created by the
system and the default
ClusterAnnotation view that we
use for any system annotation
view.
And now when we run it, it
compiles.
It runs. It launches.
And as we move them together
with no code, they merge.
And when we group them, we get
the nice little pie chart.
And that is a few tips and
tricks for how to make
AnnotationViews a lot easier.
Let's go back to the slides.
[ Applause ]
So, thank you for being here.
Today we covered a lot of
things.
First we have the new map type,
the muted standard, that allows
your data to stand out on your
map in your application.
We give you all of the map
controls as proper views so you
can configure in to display them
where and when it makes sense
with the context of your
applications.
We have the new
MarkerAnnotationView that
displays a lot more information
about your data at a glance
without having to dig in and out
of the data all the time.
And the MarkerAnnotationView
works beautiful with the new
APIs for display priority and
clustering to get your data a
lot more nicer looking and less
cluttered map.
For more information, go to this
website.
There is also the sample code
for the application I've been
showing you today.
Please play around with it and
see what great things can be
done.
I'm very excited to see what you
will do with this.
Also, go back and watch the
videos for What's New in
Location Technologies from
yesterday.
And What's New in MapKit from
2015.
Thank you very much.
[ Applause ]