MapKit makes it easy to embed a fully interactive and configurable map directly into your app. Lean about new capabilities to tailor the look of the map view and annotations, more manageable controls and new features that make dealing with a large number of annotations a snap.
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.
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 .
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.
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 ]
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.