Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2011-111
$eventId
ID of event: wwdc2011
$eventContentId
ID of session without event part: 111
$eventShortId
Shortened ID of event: wwdc11
$year
Year of session: 2011
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2011] [Session 111] Visualizing...

WWDC11 • Session 111

Visualizing Information Geographically with MapKit

App Frameworks • iOS • 43:26

The Map Kit framework lets you present rich annotated maps from within your application. Come learn about the added support for different tracking modes and new ways of showing custom information and maps to users. Review the latest best practices for integrating Map Kit into your app.

Speakers: Matt Jarjoura, Stephane Moore

Unlisted on Apple Developer site

Downloads from Apple

HD Video (412.9 MB)

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Good morning, everyone, and welcome to this presentation on Visualizing Information Geographically with Map Kit. I'd like to start by showing you some of the things that developers have done with the Map Kit API. So we've seen applications that show local content, such as restaurants and bars, to help users find what they're looking for. We've also seen new and interesting ways to visualize information, such as how light is cast at different angles depending on where you are in the world and what time it is.

We've also seen ways to track jogging information and track overall fitness of users over time. And these are just some of the creative and useful ways to use the Map Kit framework in your applications. And all of these applications were written by developers, and they all use the same basic functionality exposed by the Map Kit API.

The ability to pan and zoom to different areas of the map. The ability to annotate specific locations on the map. The ability to locate the user on the map. and the ability to overlay data on top of the map. With that, I'd like to move into the agenda for the day.

So I want to begin by talking about one of the fundamental parts of the Map Kit API, which is the map view. Then we'll talk about using annotations to show things on the map and to personalize your application. Afterwards, we'll talk about using user location to provide context to what the user is looking at. And then we'll cover the new tracking modes that have been added in iOS 5.

Then we'll talk about using overlays to put your own data on top of the map. And lastly, we'll talk about the geocoding changes in iOS 5. So as I mentioned, one of the fundamental parts of the Map Kit API is the map view. This allows you to embed a map in your application and let your users explore the world. And the specific class you'll be interested in is mkMapView.

MKMapView is a UIView subclass which you can stick in your view hierarchy. You can also add it to your nib. It's very easy to use. It handles panning and zooming gestures for you and it loads tiles automatically. And it's written to achieve high performance. Now, I just mentioned that MKMapView is a UI view.

And one of the nice things about this is that you can add gesture recognizers to it to override touch handling. In the past, we've seen some people try to subclass MKMapView in order to override touch handling, but using gesture recognizers is the right way to do this. MKMapView also lets you show different types of maps.

There are the regular maps, which show clear labels to help users orient themselves and the world. There are these satellite maps which are helpful for seeing landmarks and geographical features. and there are the hybrid maps which overlay the labels on top of the satellite imagery. Now, once you have a map view embedded in your application, you're going to want your application logic to cooperate with the map view. And to do that, you'll need an MKMapView delegate. Once you have this delegate object in your application, the MapView will be able to communicate certain events to your application.

And one of the most important events that the MapView can communicate to your application is what region of the world the user is currently looking at. Before I talk about how the MapView communicates that to your application, I'd like to first talk about how the MapView describes different areas of the world, starting with map regions. So a map region consists of a center coordinate, a latitude and longitude, As well as a span consisting of a latitudinal and longitudinal delta. And together this makes MK coordinate region. And you can use these regions to change the position of the map.

So if you set the region property, you'll be able to change the map position and scale. So here we've just zoomed in. And you should be aware that what you set on the region property may not be what the map view actually uses because it needs to choose an optimal display for your device. And as you just saw, you can animate transitions between different regions. Now, there are also cases where you may want to change the position of the map without changing the scale. And to do that, you can use the center coordinate.

So you can use the center coordinate to change the position of the map without changing the scale. And as you see here, we can pan across the map. And once again, you can animate these transitions. So you can use the center coordinate to change the map position without changing the scale, and you can use the region property to change the map position and the scale. Using the region property can result in some pitfalls. I'd like to cover some of those.

So let's imagine we're creating an application that tracks flights across the world. And in particular, we're going to focus on a flight that starts in Cameroon and goes to France. And we've decided that in order to maintain the same perspective, we're going to maintain a 30 degree latitudinal delta and a 20 degree longitudinal delta.

So we start up our application and we begin following our flight. We start over Cameroon and we head north. We move over Algeria and the perspective stays the same, so everything looks fine. But then when we move further north over France, all of a sudden the map scales out. So what just happened? Why did we zoom out? I thought we fixed a constant map region as we moved. So let's take a look at this map region as we move throughout the world.

So we started over Cameroon and we had our 30 degree latitudinal delta. And then we moved north over Algeria with the same 30 degree latitudinal delta. But as you see here, the map region has been stretched a bit. And then as we move further north over France, the region's been stretched to the point that the map view needs to scale out in order to show the full region. Why does this happen? This happens because the map view uses the Mercator projection.

The Mercator projection is a standard method for taking the globe and projecting that onto a 2D surface. and one of the intrinsic properties of the Mercator projection is that areas that are further from the equator get distorted and stretched. So this area on the globe, which looks like a square, when projected using the Mercator projection, will end up looking like a rectangle over northern Europe. So how do we get around these distortions that the Mercator projection introduces? And the way that MapKit lets you do that is by using MKMapPoint.

MKMapPoint is a coordinate in projected space which lets you represent any point on the globe. is linearly proportional to screen points, and you can use MKMapPoint for coordinate to convert from latitudes and longitudes to map points. And now that we have these projected coordinates, we can form rectangles out of them. And Map Kit calls these map rects. So a map rect consists of an origin as well as a width and a height. And together this makes MKMapRect.

And the advantage of using MapRacks is that no matter where you put them in the world, they don't change size. So here we have our map rect over Cameroon, and when we move it over Algeria, it maintains the same size. And when we move it over France, it again maintains the same size. So it's not subject to the distortions. So if we rewrote our flight tracking application using MapRex, Then we'd be able to follow our flight, maintaining the same perspective throughout.

So how do you change the position of the map using MapRects? Well, there is a visible MapRect property that you can set. And this will be able to change the map position and scale. And once again, what you set on the Visible Map Rec property may not be what the map view uses because it needs to choose an optimal display for your device. Once again, you can animate the transitions between different map recs. And you can also supply edge paddings to center content in the middle of your map. And this is useful for preventing things on the edge of the map from getting clicked.

So now we know how to get a map view into our application and how to get it to look at different areas of the world. But you should also be aware that users can interact with their map view and explore for themselves. And your application is going to want to react to those region changes by user interaction.

And the way that you'll do this is using your delegate object, mkmapview_delegate. So right before the user starts panning around, you'll get a region will change animated callback. And then the user will pan around, and at the end, you'll get a region did change animated callback. And at this point, you can use the region property or the visible map property to detect what region of the world the user is looking at.

And your application can react accordingly, perhaps by loading content specific to that region. So now we know how to get a map view in our application, and we can let the user explore different parts of the world. So let's try taking things and putting them on the map. And to do that, we'll use annotations.

So what is an annotation? An annotation describes a specific location in the world. So here we have an annotation for the Moscone West Center. And how does Map Kit represent an annotation? In Map Kit, an annotation consists of a model and a view. The model is described by the MK annotation protocol, and your model objects will need to conform to this by providing a coordinate. And this will tell the map view where to put your annotation in the world.

You can also provide a view for your annotation to tell the map view what your annotation should look like. And this will be done using MK annotation view. This is a UIView subclass. You can create your own custom annotation views, or you can use some of the built-in annotation views in Map Kit, such as the pin annotation view. So now we know what an annotation is and how MapKit represents them. So let's look at the flow for how annotations are added to the map.

It begins with your application creating a model object. Here we have a coordinate. And then this is added to the map. And at this point, the MapView will request a view for this annotation. And this will be done using the viewForAnnotation method. If you do not implement this method or do not provide an annotation view for this annotation, then a default pin view will be created for you. Otherwise, you can create your annotation view here and return that to the map.

Then the map view will take your annotation and position it in the world space. And once it's added to the map, your delegate object will get a "Did add annotation views" callback. And at this point, you're free to animate your annotations into place. For example, the pin drop animation.

So now we understand the flow of how annotations are added to the map. And we've seen an example of how to animate annotations into place. But let's look at another example of how to animate annotations into place along with some code. So here we have an implementation of our data at annotation views delegate method.

And in it, we're going to take all of our annotation views and take their target frame and animate from a single point at the base of the frame into the full frame. And in the end we'll get something that looks like this. So our green cone grows into the map.

And now that we have our annotations on the map, users are going to be curious about them, and they're going to want to interact with them and find out more about them. And that's where selection comes into play. So when a user taps on an annotation, your delegate will get a "did select annotation" view callback. And when they tap away to look at something else, you'll get a "Did Deselect Annotation View" callback.

And if your application wants to draw attention to a particular annotation without the user interaction, then you can use the select annotation and deselect annotation methods on the map view. And now that the user has selected an annotation, they're going to want to find out more about that annotation. And one of the best ways to do this is by showing a callout.

So Map Kit provides support for default callouts. And the way that you can access this is by setting the can show callout property on your annotation view, at which point your model object will need to provide a title. And once you do, you'll be able to show a callout like so, Moscone West. You can also optionally provide a subtitle. And that will pop in right underneath. Attending WWDC. And you can also add callout accessories. For example, here we have a details disclosure button.

So I've just described some of the default selection behavior that you can opt into, but you can also implement your own custom selection UI. And you can do this by subclassing empty annotation view and overriding setSelected, or by taking the delegate methods didSelectAnnotationView and didDeselectAnnotationView and kicking off your animations from there. And here we have an example where when I select this green cone, it grows. But selection is only one of the ways that users can interact with your annotations. There's also support for Jaguar annotations, which we added in iOS 4.

And the way that you can support draggable annotations is by setting the draggable property on your annotation view, at which point your model object will need to implement set coordinates so they can change locations on the map. And once you do so, here we have a purple pin, and the user can pick it up and move it across the map.

And you can also provide your own custom animations for dragging. And the way that you can do this is by subclassing mkAnnotationView and overriding setDragState. And here we have an example where we have our green cone again, and when I pick it up, it'll grow, and when I drop it, it'll shrink back.

So I've just shown you a number of ways that you can personalize annotations in your application and animate them. But there are a couple of ways that you can animate annotations that I want to call to attention. So Map Kit uses key value observation on your annotations to detect changes to certain properties. So if your model object, for example, has a key value observable coordinate and you change this coordinate in an animation block, then the map view will be able to slide your pin across the map.

You can also animate changes to title and subtitle. And you can also animate changes to call out accessories. To learn more about key value observation, you can look at the documentation online. And I should also mention that if you use the synthesized syntax on your properties, you can get KVO by default. So now we know how to get annotations in our application and how to customize and personalize them. And we'd like to showcase some of the things that you've seen so far with a demo. And for this demo, I'd like to call up Matt Jarjoura to the stage.

Okay, I just want to let everyone know that you can go ahead and download the sample that I'm going to walk you through today. It should be available right now. And I'm going to be walking you through a sample of how to take annotations, or take a collection of photos that have GPS data in them and place them on the map at the coordinates that the photos were taken. So the first thing you want to do is Load all the photos that you have. And in this sample, I just placed them inside of the application bundle.

And I'm going to spin off a dispatch queue here to load the photos. And I'm doing this on a secondary thread so that it doesn't stop the map from being able to interact. And to load the photos, it's pretty simple. You simply load the photo and then I can ask for the latitude and longitude and store this in a CL location coordinate. and then place this into a photo annotation object. Now just like Stephan showed you, a photo annotation object is a simple NS object. Uh, that we just follow the MCAnnotation protocol. And then here's a coordinate.

So then, now I build up my array of photos, and I simply, after I'm done, I place those into the map view, add annotations. So when I run my application, I now have annotations on here with the photo. And I did set the title of the callout to the actual file name of the image. As you can see here.

But it's not very interesting, and I think we can do some more things here. So I want to go ahead and add the ability to view the photo that this annotation is referencing. And I'm just going to need to implement some delegates. The first of them is view for annotation. Now what I want to have in my application is a way to bring the call out up and tap on the accessory button.

And here to do that, I'm going to use our standard MKPin annotation view. Now this is actually in Map Kit, so you can use this. It's what we saw earlier. But you'll notice here I had to explicitly say can show call out. If you want to use a view for annotation and actually implement the annotation yourself, you're going to have to specify can show call out.

We do it by -- we do it for you if you don't specify it, but you're going to need to in this case. And then simple enough, we're going to use a standard UI button with type UI button type detailed disclosure. And then we're going to set our right callout accessory view to that disclosure button.

And then, now, I want to, whenever the user taps on that accessory button, I want to bring up this Photos View Controller. This is in the sample application. I'm not going to go over that today. It's sort of outside the scope of this example and for time, too.

But it will bring up a new controller to show you the actual photo. I'm going to go ahead and run this again. And now when I tap on any of these annotations, I have my new accessory here. I can tap on it and actually see the photo that it's referencing.

So now let's give a little bit more of a real world example. Close this out of the way. To do that, I'm going to change my photos loading here to large photo sets. Run it again. And now we have hundreds of photos. So this is the simulator, so you're not going to necessarily see the performance degradation here, but if you did run it on a device, you would notice a lot of frames dropping in this case. Each one of these is a UI view. But also, aside from performance, too, this is no longer usable. It's almost impossible for your user to try to get at the pen that they're talking about.

So if you're familiar with the Photos app today, what they do is clustering. They take all of the annotations and they split them up into a grid and say, OK, what is a size that we think also helps performance and also helps your user be able to locate the annotation that they want to? So I'm going to show you how you can do that now with your application.

Sorry. So I've created this update visible annotations. And this gets called any time I move the region changed on the map or when I add annotations to it. So what I want to do is set up a grid and figure out the perfect space that I can basically narrow or cluster my annotations down to.

Sorry. So here, I'm going to use about 60 by 60 pixels, and then I'm going to go through each of the grid squares and pull out my annotations and then coalesce them into one annotation. So in order to do this, in a real-world case, you probably want to set up a quadtri so that you can take the annotations and put them into that and then pull them out from there. But I think in a simple example like today, and if your application is relatively simple, I'm going to show you a little trick that you can use. And that's to place all of the on-screen annotations into this off-screen map view. So here we have add annotations.

I'm going to use this all annotations map view, which when I create it and viewDidLoad, You'll notice here that it's of a rec size zero, so it shouldn't, and we're not going to actually put it on screen, so it shouldn't have any impact with your view hierarchy. So that way now, we're going to add all of our annotations to this off-screen one, and we can ask a new API that was added in 4.2, annotations in MapRect. So now I can get all the annotations in my grid MapRect in this off-screen map view, and then I'm going to call this method annotationInGrid, which I'm going to show you right here.

And what I'm going to do is handing all the annotations that I have on screen already on my on-screen map view, and if I do have an annotation in that grid, I'm going to return that. If I don't, then I'm going to go to this new off-screen map view, and I'm going to find the center-most annotation that I have. And then I'm going to return that one.

And then once I have that, I'm going to go ahead and add that to my map view, my on-screen map view here, you can see. And then if my on-screen map view had any other annotation that was in that grid space, I'm going to go ahead and remove it. So now let me go ahead and show you the application.

And in my Photos View Controller, I added the ability to show multiple photos. So then if I zoom out, you'll see they go away. Or if I zoom in, it updates and adds the new ones on the screen. Okay, so if you've used the Photos app, you'll also notice that they do a nice animation, and that's to give it a little bit more polish, I think, and to not be so visually jarring for your users so they can understand what they're looking at when they zoom at different levels here. So I'm going to add some simple animation. First one is I'm going to use another map view delegate.

Here, so it's going to say if this, if this annotation at the current level or the previous level had, was in a cluster, Then I'm going to take its clustered annotation location and animate it to its new location with a simple UI view animate with duration. And then back up here where I'm removing the annotation, the same thing.

If I go up a level, then I'm going to do the same thing. If it's going to move into a clustered annotation, I'm going to remove it from its old location and animate it to its new location with a UIView annotation. This mouse is not working. So now when I zoom out, they animate out.

[Transcript missing]

So we've just seen that we can show a lot of information on the map. And sometimes it helps to provide context to what the user is looking at. And one of the nice ways you can do this is by showing user location on the map. So how do you get user location on your map? It's very simple.

There is a shows user location property on the map view. And once you set this, this will require location services authorization. So a location services alert will pop up to ask the user whether this application is allowed to use their current location. And if they accept, then we'll be able to show the user location on the map, and your delegate will get a "did update user location" callback.

And if for some reason they were to deny access to their current location or we were unable to get a quick location fix, then you'll get a "did fail to locate user with error" callback. So it's really easy to add user location to your map. But there are a couple other things you should know about user location.

For instance, user location is an annotation, and you can access it using the user location property. And you can actually provide your own custom annotation views for it. And if the user pans away to look at something else, you can use the read-only user location visible property to detect whether the user's location is on screen.

Now, in the past, we've seen that it sometimes takes a lot of boilerplate work to keep the user's location on screen and keep information relevant to where they are. So in iOS 5, we've added support for tracking modes. So how do you get tracking modes working in your application?

Once again, it's very easy. There is a user tracking mode property on the map view. This requires the same location services authorization that user location does. So an alert will pop up to ask the user whether this application can use their current location. And we've also added the tracking bar button item that you've seen in the Maps application.

And this is useful for showing the user which tracking mode they're in, as well as letting them transition to different tracking modes. So if the user permits use of their current location, then you'll be able to enter tracking mode. and follow their location. So here we're smoothly transitioning, following along 280 going north.

And we also support tracking, location, and heading. And this is a nice way to provide perspective to what the user is looking at and to show user-centric data. But up until now, we've been talking about how to describe single points on the map. But not everything can be described by single points. And that's why Map Kit provides support for overlays.

So what is an overlay? It's spatial data that you can put on top of the base map. And how does Map Kit represent overlays? Similar to annotations, it consists of a model and a view. The model is described by the MK overlay protocol, and your model objects will need to conform to this by providing a bounding map rect and a center coordinate. And this center coordinate is useful if you want to annotate your overlay. And the reason that this center coordinate is not derived from the bounding map rect is best illustrated by example.

So here we have a triangle overlaid on top of California, and we have a pin representing the center of the bounding map rect, and it's along one of the edges of the triangle. Whereas the true center of our triangle is actually offset from that center of the bounding map rect. And that's why we let you specify the center coordinate independent of the bounding map rect.

You'll also need to provide a view for your overlay. Unlike annotations, you are required to provide a view for your overlay. And you'll do this using mkOverlayView. And the important method that you'll need to implement is draw map rect, zoom scale, and context. And this will tell the map view how to draw your overlay in certain regions of the world. Now, Map Kit also provides support for a number of built-in overlays. There is MK Circle. MKPolyline and MKPolygon. And each of these overlays has a corresponding overlay view. So let's look at some code for how to add these to the map.

So here we have our viewDidLoad method. And in it, we're going to take a center coordinate and then create an MK circle with a radius of 200 meters around that center coordinate. And then we'll add this to the map. And at that point, the map view is going to request a view for this overlay. And this will be done using the viewForOverlay delegate method.

And as I mentioned before, you're required to provide a view for your overlay. So we're going to create an MK Circle view. We'll set a stroke and a fill color. And then we'll return this to the map. And in the end, we'll get something that looks like this.

So it's a circle around the center coordinate we specified with the radius we specified. There's a detailed talk from last year which covers overlays in more comprehensive detail. If you'd like to learn more about overlays, I'd encourage you to download those videos from iTunes U and the Apple Developer Connection. But now I'd like to talk about the geocoding changes in iOS 5. So first of all, what is geocoding? Well, there's actually two types of geocoding. There is forward geocoding and reverse geocoding. So what's forward geocoding?

Forward Geocoding takes an address and then turns that into a coordinate. And an example of that would be when you go to address book and you tap on an address and it shows you that address on the map. That's an example of a forward geocode. So what's reverse geocoding? Well, it's just the opposite. You take a coordinate and you turn that into an address. And an example of that would be when you drop a pin and it shows you the address information for that location.

Map Kit has provided support for reverse geocoding through the MK Reverse Geocoder class since iOS 3. and developers have been asking for forward geocoding for some time. We've listened to these requests and we've decided to act on it. And we're pleased to tell you that in iOS 5, MK Reverse Geocoder is deprecated.

But that's OK, because in iOS 5, there's a new geocoding API. And it's in core location. And it's called CLGeocoder. And it provides support for both forward and reverse geocoding. So how do we use this new geocoding API? Well, for forward geocoding, there's two methods. There is geocodeAddressString and geocodeAddressDictionary. And for reverse geocoding, there is reverseGeocodeLocation. Each one of these methods takes a completion handler. What is a completion handler?

Well, a completion handler is simply a block to tell the geocoder what to do once the geocode completes. So on success, you'll get an array of CL placemarks, and these placemarks will be sorted with the most confident results at the beginning of the array. And if the geocode should fail, then you'll get an NSError back. So I just mentioned that you'll get an array of CL placemarks back. So what do those look like?

A CL placemark has a location as well as a number of address properties that describe that location. Now, some of you have been using the MapKit API in the past will remember that there was an MK PlaySmart class which represented reverse geocode responses. So what happens to that?

Well, in iOS 5, MKPlacemark is now a subclass of CLPlacemark. That means it supports all of the existing properties, as well as the new ones on CLPlacemark. You can also create MK placemarks from CL placemarks, and you can do that using the initWithPlasemark method. MK placemarks are annotations, so you can add them to the map. And you can also still use the dictionary representation with address book. So now we know more about the new geocoding API, so let's look at some code for how to use it, starting with reverse geocoding.

So it's very simple. We create a location object using a latitude and longitude. We then allocated Geocoder, and then we reversed Geocode the location we created. And then we provide a completion handler, which will inspect the placemarks that we get back. And it will take the most confident placemark at the beginning of the array.

And then it'll create an MK placemark from that and then add that to the map. So it's very simple to use the new reverse geocoding API. Now what about forward geocoding? CL Geocoder also supports that. And as I mentioned before, it supports geocoding address strings and address dictionaries. So let's start with an example of how to forward geocode address strings.

So first we create our address string, one infinite loop, Cupertino, CA. And then we allocate our geocoder. And then once again, we geocode the address string and provide a completion handler, which will take the most confident placemark, create an MK placemark from it, and then add that to the map.

So once again, it's very easy to use. Now what about address dictionaries? Well, it's very similar. We allocate an address dictionary, which is a more structured definition of an address. So here we have one infinite loop for the street, Cupertino for the city, and the abbreviation for California for the state. And then we allocate our geocoder, we geocode the address dictionary, and once again we provide a completion handler which takes the most confident result and creates an mkplace mark and adds that to the map.

So again, very easy. And one of the reasons you may want to use an address dictionary for your forward geocodes is that this more structured definition of an address allows the server to make more assumptions about the data and may return more accurate results. So now that you know how to use the new CL Geocoder API, let's revisit the application we created earlier and add geocoding to it.

And I'd like to call Matt Jarjoura back up to the stage for this demo. Okay, so we're going to go this time back to our photo annotation. And because we have our coordinate coming in already, stored in this annotation, I'm going to use that coordinate to reverse geocode its actual location.

To do that, I'm going to pass the coordinate into a CL location object, and then I'm going to create a new CL geocoder, and then pass that location into reverse geocode location. Then once I have the location, I'm going to get a string, just a simple helper method here. And all this helper method does is take the city and state and places them together with a comma. And then set the subtitle of my current callout to that updated location. There's only one other piece to make this work. I need to implement another delegate.

did select annotation view so that when I tap on the annotation callout, it will begin the reverse geocoding process. So now when I run the application, when I tap on a callout, You should see how it sets the subtitle here of the callout. I also added this into the photo viewer as well so that it reverse geocodes the photo.

There you go. Thank you. Thank you, Matt. So you see, it's really easy to get started using the new geocoding API. And now I've talked about all the topics I wanted to talk about today. So let's summarize what we've covered. Map Kit lets you show rich annotated maps with powerful contextual information.

You can use the new tracking modes in iOS 5 to give perspective to what the user is looking at and show user-centric data. You can use overlays to put your own spatial data on top of the map. and you should adopt the new geocoding APIs in iOS 5 to get a better user experience.

There are a couple other sessions you may be interested in. There is the What's New in Core Location session, which took place yesterday. You'll be able to download videos of that later on. There is the Testing Your Locationware Application Without Leaving Your Chair session, which takes place on Friday.

If you have any other questions, please contact Bill Dudney, our Application Frameworks Evangelist. You can also consult the Map Kit and Core Location Framework documentation. And if you have any other questions, you can ask them on the Apple Developer Forums. That concludes the presentation. Thank you for your time.