Streaming is available in most browsers,
and in the Developer app.
-
Unlock the power of places with MapKit
Discover powerful new ways to integrate maps into your apps and websites with MapKit and MapKit JS. Learn how to save and reference unique places using Place ID. Check out improvements to search that make it more efficient to find relevant places. Get introduced to the new Place Card API that lets you display rich information about places so customers can explore destinations right in your app. And, we'll show you quick ways to embed maps in your website with our simplified token provisioning and Web Embed API.
Chapters
- 0:00 - Introduction
- 0:54 - Reference a place
- 6:12 - Display place details
- 12:05 - Find a place
Resources
- Displaying place information using the Maps Embed API
- Forum: Maps & Location
- Identifying unique locations with Place IDs
- Interacting with nearby points of interest
- Resources - Apple Maps - Apple Developer
Related Videos
WWDC23
-
Download
Welcome! I'm Mike, and howdy, I'm Jeff. We're MapKit engineers, and today, we're excited to talk you through some of the new features that you can find in both the MapKit framework and in MapKit JS for the web.
A lot of the time, a map is really all about the places that you find on the map. When you navigate or explore places using Apple Maps, you have access to a wealth of information about the places throughout the world.
This year, MapKit has added features that enable you to bring these places into your own apps in new ways.
There's a lot to cover! We'll focus on three primary use cases.
Specifically, we'll talk about how to reference places using an identifier, how to show details about a place in your app's UI, and how to find places effectively using enhancements to our search APIs.
First up, I'll tell you about how to reference a place. This year, we're introducing an identifier for places. This Place ID can be used to reference the places represented by map items in the MapKit framework, and by Place in MapKit JS.
A wide variety of places can be referenced using Place ID. You can use it for points of interest such as museums, restaurants, parks, or schools. An app might use it to reference the book stores that an author will visit on their upcoming new release book signing tour.
The references are unique, and they remain valid over time; they're designed to last as long as the place does.
The book signing tour app might want to offer a website link for each store on the tour. Any time Apple updates the data for a store, like the website URL, an app using Place ID can display that fresh information. Because these identifiers are unique, the app can use them as keys in its own data structures. Identifiers can be persisted and shared. Apple keeps track of the data, and you can reference it any time.
You were telling me about an app you've been working on that uses a lot of our new features, including Place ID. Can you show us what you've been building? Sure thing! My app is all about collecting places, so it really benefits from Place ID. Introducing My Apple Store collector! I love trekking across countries to collect the most rare and shiny of Apple Stores, and with Place ID I think I can fill out a lovely store index of them. All collections have to start somewhere, and what better starter than the Apple Park Visitor Center. To get my index started, I need to find the Place ID for the Center. To find a Place ID, I could use an existing API like Search or Geocoding. Instead, I'll use the new Place ID Lookup tool, since the tool is faster for looking up just a few IDs.
I'll start by going to developer.apple.com/maps and clicking the Resources link in the top right.
On the resources page, I can scroll down and find Place ID Lookup! Here I can easily search for the Apple Park Visitor Center, or I can just tap it on the map to view its Place ID. Great! Now that I have the ID, I want to show the place on a map in my app. I can do that with just a few lines of code! This is the code I'm using to display the Apple Park Visitor Center. Here, I create an identifier with the Place ID I got from the lookup tool.
That identifier is used to fetch a map item using an asynchronous request. The app simply displays a map with a marker containing that item. By using Place ID here instead of a hard-coded coordinate, I'm leveraging the strength of the Apple Maps editors, meaning, for instance, if they ever decide the place was misplaced and the marker needs to be moved, I'll inherit their change without having to update my code.
I can share this Place ID with all of my native apps and web content too! Here's the JavaScript code I'll use to create an equivalent display on the web.
First, I create a function called entryPoint. I'll tell MapKit JS to call this function after it's been initialized.
In that function, I pass the ID I found on the Place ID Lookup web site to a new function that looks up the corresponding place and sends it to my callback, annotatePlace in this case.
That annotatePlace function constructs a new map centered around the place and then adds a new kind of annotation made specifically for displaying place objects: the PlaceAnnotation. Next, I'll tell MapKit JS to call my entryPoint function after it has loaded. This year, it takes less code than ever to initialize MapKit JS.
To get started, I just add this asynchronous script tag with the MapKit JS src attribute and list my entryPoint function as the data-callback so MapKit JS will call it after loading.
The last attribute is data-token. But where does this token come from? This year we're replacing the dynamic JWT generation process with a streamlined UI that generates domain-specific token strings that don't have to expire until you manually revoke them. I'll make a new token with our new token provisioning tool! I'll start by going to developer.apple.com/maps and clicking the Resources link in the top right.
On the resources page, I can scroll down and find Create a Token! By clicking the add button I can fill out a short form to create a MapKit JS production token that doesn't expire, and is only valid on my domain.
Once I've created a token, I can come back here later and revoke it if it's no longer valid. From there, I can just copy the token right into my script tag, and not worry about expiration dates, dynamic JWT generation, or even calling mapkit.init ever again. Looks like I'm all set up to show off my starter Apple Store in my app and on the web. Your app's off to a great start! Being able to reference an individual Apple Store using the same identifier on your app and web site is pretty sweet. There are so many things you can do using Place ID. To learn more about how this technology can be used to build and maintain your own collections of Place IDs, please check out the Identifying Unique Locations With Place IDs link in the resources associated with this talk.
So, using Place ID, you'll have fresh information for the places you care about. Next up? Display that place information in your app or website.
When you tap on a place using the Maps app, it displays a Place Card. The Place Card shows information such as opening hours, phone number, and more.
This year, MapKit brings Place Cards to your apps and web sites.
Our new SelectionAccessory APIs can be used to display place information any time a place is selected on the map.
Information can be displayed in a large, full presentation, a space-saving compact presentation, or just a link to view the place in the Maps app. You can choose a style that works best for your use case, or simply use the default automatic style and let MapKit pick the best one for the platform and size of the map view.
You can present Place Cards even if your app or website doesn't feature a map view.
The MapItemDetail and PlaceDetail APIs offer flexibility, and support a wide variety of use cases. Finally, embeds are a quick way to add a map to your website.
The Create a Map tool on the developer website simply generates HTML that you can copy and paste.
Mike has been bringing Place Cards to his app. Show us what you got! Definitely! Now that I have the Visitor Center showing on a map, I want to display beautiful, up-to-date information about it! Here's a map of all the stores I've visited in my app. I'll add one line of code and voilà! Now, when I tap one of the visited stores, my app shows lots of detailed information about the store. Beautiful place data is valuable everywhere, so let me update my website from before too. I've modified my MapKit JS code from before by adding two lines.
The first creates a new PlaceSelectionAccessory and the second attaches that accessory to my existing annotation from before.
Now, when the Apple Park Visitor Center is selected, rich place data pops up.
Using MapKit JS to display this information is great for pages that have multiple points of interest or an otherwise complex usage pattern, but for websites that just display a single place's data, I can use that new low-code solution that Jeff mentioned: embeds! Since my website just shows the details for a single Apple Store at the moment, I'd like to figure out how I could get that same functionality with an embed.
I'll start by going to developer.apple.com/maps and clicking the Resources link in the top right.
On the Resources page, I can scroll down and find Create a Map! Here, I can select the new Web Embed button to customize an embedded map with my Place ID, developer token, and more.
Once the map displays what I want, I can quickly copy the generated HTML snippet onto my website to get a beautiful map without writing a single line of JavaScript! The last way I can display rich place information is outside of the context of a map entirely. Sometimes I want a quick scrollable list of every store I've collected rather than a map, and I think that each list item would be much more helpful with place information.
Here's my code to display a list of my favorites. First, I show a list of all my stores where each list item displays its respective store's name.
Using this line, my list items now show a full Place Card when tapped! I may also want to see place details for my favorites on the web, so I'll update my website too. Here I've taken the web code I had previously, but I replaced the annotatePlace function with just a couple statements.
The first grabs an element from the page and the second fills the element with details about the place I looked up. I'm also using the new adaptive color scheme, which allows this place detail to match the system's preferred color scheme: light or dark. This same value can now also be used to make adaptive MapKit JS maps. My app and website are really coming together! What do you think? They're coming together indeed! You're using our new APIs to show place information for the stores you've collected. The MapItemDetail API can be used to present a Place Card, even if your app doesn't feature a Map view. APIs are available for UIKit, AppKit, and SwiftUI. With MapKit JS, you can use the PlaceDetail API to include a Place Card as content in a web page. The SelectionAccessory APIs can be used to show place information right on the map.
MapKit offers SelectionAccessory APIs for use with a variety of platforms and technologies.
Looking at the map with all your favorite stores kinda makes me want to follow in your footsteps and visit one of them. I think it'd be fun to bring my daughters, and let them pick out their very first Apple t-shirts.
They love a good picnic, too, so maybe we'll find a park and grab a bite after.
I have an idea, using the SelectionAccessory API, you can enable Place Card display for all of the other places on the map; not just the Apple Stores. I can just tap around the vicinity of each of your favorite stores until I find a nice looking park for a picnic. That'll help me choose the best Apple Store for us to visit! Let's see. Your app adds a Marker for each of the map items representing your favorite stores.
In SwiftUI, you can support selection of both your own content and map features found on the map itself by using MapSelection.
You can display a Place Card callout when map features are selected by using this modifier.
This is a great way to get started with the SelectionAccessory API; simply enable it for map features in your existing app code. Your app can present annotation content just as it already does, and your customers can learn more about the places found near your content on the map.
This additional context can provide lots of value.
So, now you know how to reference places with Place ID and display useful information with a Place Card, but what places? If you aren't featuring only the specific places that you found with the Place ID lookup tool, you'll probably need your app to search for some places! We've made improvements to our search API this year, including new ways to filter search results. You can search for more kinds of places, like music venues, skate parks, or even castles.
You can search for physical features, such as rivers or mountain ranges. You can even search for specific components of an address, such as a city or a postal code.
We've enabled searches that are restricted to a specific bounding region, so you can focus on just the area that matters. And for our server API, we've added pagination. This allows for searches with a ton of results. Pages of them, in fact! Do you think these new search features might help you find and collect stores more efficiently? Absolutely! After spending all day hunting through the Bay Area for wild Apple Stores, any collector might need some strong coffee to keep on going! I'll use those new search features on my web site to make sure I can plan ahead to be as caffeinated as I want to be. Then I'll add the same features to my native app. First, I need to find Cupertino. It sounds like I can do that with our new AddressFilters! Here I create an AddressFilter to only search for localities which are cities in most countries.
Then, I plug that filter into a new search object.
Finally, I run that search with the term Cupertino to find the city I'm looking for without any risk that I might find a business called Cupertino's Classy Cleaners.
Those search results for cities matching Cupertino get sent into a showMap function that I set up.
In that function, I take the first search result and use it to create a region I pass into a new map.
That code centers the map right over Cupertino, which is exactly what I wanted. Then, I create a new search within that region and search for coffee.
And finally, I add a new PlaceAnnotation for every coffee result. Every result is now marked on the map! But there's still a bit of a problem. When I zoom out I notice that I've added lots of annotations to the map outside of the initial viewport. I really don't need all these distant coffee places, so I can use a RegionPriority to remove them! With just this one property, I can require that the results reside within the region I passed into the search.
After that change, the results on my page are limited to just what's in my original region, which are the only ones I could really bike to anyway. Of course, all of these features are available to native apps as well. I'll enhance my app in exactly the same way. Here I've created a view that will display markers for each coffee shop. Asynchronously, I use findCity to get a city region, and then findCoffee to find coffee shops in that region. Here's findCity, it searches for places with an addressFilter just like the JS code did. And here's findCoffee, where we take that Cupertino region and use a regionPriority to search for coffee shops that must be within the region. All right, I think with these improvements I'm well on my way to being the very best Apple Store collector. I'd say so! I'll be excited to see how far you'll go to grow your collection. You might even end up collecting passport stamps! Alright! We've covered a lot of ground! The latest version of MapKit makes it easy to reference places with Place ID, show useful information with Place Cards, and even embed a place in a web page.
You can use it to search for places; so many kinds of places, and you can restrict your search to a specific area.
The MapKit server API offers more results than ever with pagination.
MapKit JS offers a simplified token provisioning process, making it a lot easier to get started.
Before we go, let me give you some homework. Use Place ID to reference the park where the farmers market will set up on Saturday morning.
Display place information in your apps with Place Card.
Place Card will bring depth to an app's search results, and to the specific locations that matter to you. Enable Place Cards for all of the points of interest on the map to provide valuable context to the people using your apps to explore an area. Be sure to check out the Identifying Unique Locations With Place IDs link for even more details about how to use Place ID! We hope you've enjoyed learning about the new features in MapKit. Keep on mapping, and I'll keep on collecting!
-
-
3:06 - Display a visitor center annotation
// Display a visitor center annotation struct PlaceMapView: View { var placeID: String // "I63802885C8189B2B" @State private var item: MKMapItem? var body: some View { Map { if let item { Marker(item: item) } } .task { guard let identifier = MKMapItem.Identifier( rawValue: placeID ) else { return } let request = MKMapItemRequest( mapItemIdentifier: identifier ) item = try? await request.mapItem } } }
-
3:44 - Display an annotation for the center
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const id = "I63802885C8189B2B"; const lookup = new mapkit.PlaceLookup(); lookup.getPlace(id, annotatePlace); }; const annotatePlace = (error, place) => { const center = place.coordinate; const span = new mapkit.CoordinateSpan(0.01, 0.01); const region = new mapkit.CoordinateRegion(center, span); const map = new mapkit.Map("map", { region }); const annotation = new mapkit.PlaceAnnotation(place); map.addAnnotation(annotation); }; </script> <div id="map" style="width: 100dvw; height: 100dvh;"></div> </body> </html>
-
7:32 - Display my favorite apple stores
// Display my favorite apple stores struct VisitedStoresView: View { var visitedStores: [MKMapItem] @State private var selection: MKMapItem? var body: some View { Map(selection: $selection) { ForEach(visitedStores, id: \.self) { store in Marker(item: store) } .mapItemDetailSelectionAccessory() } } }
-
7:50 - Display a selectable annotation
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const id = "I63802885C8189B2B"; const lookup = new mapkit.PlaceLookup(); lookup.getPlace(id, annotatePlace); }; const annotatePlace = (error, place) => { const center = place.coordinate; const span = new mapkit.CoordinateSpan(0.01, 0.01); const region = new mapkit.CoordinateRegion(center, span); const map = new mapkit.Map("map", { region }); const annotation = new mapkit.PlaceAnnotation(place); map.addAnnotation(annotation); const accessory = new mapkit.PlaceSelectionAccessory(); annotation.selectionAccessory = accessory; }; </script> <div id="map" style="width: 100dvw; height: 100dvh;"></div> </body> </html>
-
9:15 - List stores and show details when selected
// List stores and show details when selected struct StoreList: View { var stores: [MKMapItem] @State private var selectedStore: MKMapItem? var body: some View { List( stores, id: \.self, selection: $selectedStore ) { Text($0.name ?? "Apple Store") } .mapItemDetailSheet(item: $selectedStore) } }
-
9:37 - Show visitor center details
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const id = "I63802885C8189B2B"; const lookup = new mapkit.PlaceLookup(); lookup.getPlace(id, annotatePlace); }; const annotatePlace = (error, place) => { const el = document.getElementById("place"); const detail = new mapkit.PlaceDetail(el, place, { colorScheme: mapkit.PlaceDetail.ColorSchemes.Adaptive }); }; </script> <div id="place"></div> </body> </html>
-
11:17 - Display a place card for the selected map feature, too
// Display a place card for the selected map feature, too struct VisitedStoresView: View { var visitedStores: [MKMapItem] @State private var selection: MapSelection<MKMapItem>? var body: some View { Map(selection: $selection) { ForEach(visitedStores, id: \.self) { store in Marker(item: store) .tag(MapSelection(store)) } .mapItemDetailSelectionAccessory(.callout) } .mapFeatureSelectionAccessory(.callout) } }
-
13:09 - Find Cupertino, then find coffee
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> #map { margin: 0 auto; } </style> </head> <body> <script crossorigin async src="https://cdn.apple-mapkit.com/mk/5.x.x/mapkit.js" data-callback="entryPoint" data-token="TODO: Add your token here" ></script> <script> window.entryPoint = () => { const addressFilter = mapkit.AddressFilter.including([ mapkit.AddressCategory.Locality ]); const citySearch = new mapkit.Search({ addressFilter }); citySearch.search("Cupertino", showMap); }; const showMap = (error, cities) => { const center = cities.places[0].coordinate; const span = new mapkit.CoordinateSpan(0.01, 0.01); const region = new mapkit.CoordinateRegion(center, span); const map = new mapkit.Map("map", { region }); const coffeeSearch = new mapkit.Search({ region, regionPriority: mapkit.Search.RegionPriority.Required, pointOfInterestFilter: mapkit.PointOfInterestFilter.including([ mapkit.PointOfInterestCategory.Cafe ]) }); coffeeSearch.search("coffee", (error, results) => { for (const place of results.places) { const marker = new mapkit.PlaceAnnotation(place); map.addAnnotation(marker); } }); }; </script> <div id="map" style="width: 100dvw; height: 100dvh;"></div> </body> </html>
-
14:41 - Finding coffee in Cupertino
// Finding coffee in Cupertino struct CoffeeMap: View { @State private var position: MapCameraPosition = .automatic @State private var coffeeShops: [MKMapItem] = [] var body: some View { Map(position: $position) { ForEach(coffeeShops, id: \.self) { café in Marker(item: cafe) } } .task { guard let cupertino = await findCity() else { return } coffeeShops = await findCoffee(in: cupertino) } } private func findCity() async -> MKMapItem? { let request = MKLocalSearch.Request() request.naturalLanguageQuery = "cupertino" request.addressFilter = MKAddressFilter( including: .locality ) let search = MKLocalSearch(request: request) let response = try? await search.start() return response?.mapItems.first } private func findCoffee(in city: MKMapItem ) async -> [MKMapItem] { let request = MKLocalSearch.Request() request.naturalLanguageQuery = "coffee" let downtown = MKCoordinateRegion( center: city.placemark.coordinate, span: .init( latitudeDelta: 0.01, longitudeDelta: 0.01 ) ) request.region = downtown request.regionPriority = .required let search = MKLocalSearch(request: request) let response = try? await search.start() return response?.mapItems ?? [] } }
-
-
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.