DEV Community

Cover image for CarPlay: Linking to the Maps App
Simon Rice
Simon Rice

Posted on • Updated on

CarPlay: Linking to the Maps App

I've recently been granted a CarPlay developer entitlement from Apple 🎉 and as a result been working on a little app for this platform. On first impression, its development platform has shades of legacy WatchKit development due to the heavy reliance of adding views (or, in this instance, populated templates) to an "interface controller", except without any use of Interface Builder, and definitely no form of SwiftUI at the time of writing! Indeed the best way of seeing CarPlay is like a second display for your app the provides a very specific set of functionality - that in itself sounds a lot like the early days of Apple Watch!

When being granted your CarPlay entitlement, you get pointed to two resources:

  1. Apple's CarPlay Programming Guide
  2. WWDC 2020: Accelerate Your App with CarPlay

From my experience, combined together these give an extensive guide to developing on CarPlay. However, I've noticed a small omission that I'd like to write about.

One template for CarPlay I've been using is the Point of Interest template (CPPointOfInterestTemplate), for which there literally is a point of interest about this template in the WWDC video:

When a location is selected from a list, you may choose to show a secondary card for that point, revealing additional information and up to two buttons.

These buttons could be used for a variety of tasks. For instance, they may signal you to switch to a navigation app for driving directions or push to another template to display additional information for that location.

Now interestingly neither the video nor the programming guide state how to switch to a navigation app!

CPPointOfInterest has 2x optional properties relating to buttons that show up when you have a secondary card displayed - primaryButton and secondaryButton. These are both of type CPTextButton. In CPTextButton's initialiser, there's a handler parameter that takes a closure. But what should go there if you want to open the point up on your maps app so you can route to that location?

Now chances are you already have an MKMapItem since CPPointOfInterest requires one in its initialiser. This class has a method of interest, specifically openInMaps - this sounds very promising.

So let's try it out:

let mapItem = MKMapItem(placemark: ...)
let poi = CPPointOfInterest(
    location: mapItem,
    title: "Sample Location",
    subtitle: "Subtitle",
    summary: "Summary",
    detailTitle: "Detail Title",
    detailSubtitle: "Detail subtitle",
    detailSummary: "Detail Summary",
    pinImage: UIImage()
)

poi.primaryButton = CPTextButton(
    title: "GO",
    textStyle: .normal,
    handler: { _ in item.openInMaps() }
)

let mapTemplate = CPPointOfInterestTemplate(
    title: "Locations", 
    pointsOfInterest: [poi], 
    selectedIndex: NSNotFound
)
Enter fullscreen mode Exit fullscreen mode

The template will now show one location...

Simulator Screen Shot - iPhone 11 Pro Max - 2020-10-18 at 15.06.16

...and you can drill down to the location in question...

Simulator Screen Shot - iPhone 11 Pro Max - 2020-10-18 at 15.06.19

...tapping "GO" will open up the maps app - unfortunately this happens on your phone - and crucially Apple will reject your app because Apple rightly states:

All CarPlay user flows must be possible without interacting with iPhone.

Thankfully this can be fixed - MKMapItem.openInMaps(...) can optionally take a few parameters:

  • launchOptions - there are a few optional launch parameters you can pass to the maps app to help with directions. More on this a bit later.
  • scene - the scene that you're launching the maps app from.
  • completion - an optional completion handler that is called upon the maps app being loaded.

Now the scene parameter looks promising. Chances are you'll be calling all of this from CPTemplateApplicationSceneDelegate.templateApplicationScene(:didConnect:), which has a CPTemplateApplicationScene parameter. Conveniently this is a UIScene subclass, so you can either pass this directly in or retain it from your didConnect method.

So let's now populate that scene parameter - again assuming this is called from didConnect:

poi.primaryButton = CPTextButton(
    title: "GO",
    textStyle: .normal,
    handler: { _ in 
        item.openInMaps(
            from: templateApplicationScene
        ) 
    }
)
Enter fullscreen mode Exit fullscreen mode

This indeed opens up the maps app with the suggested location prepopulated. However, as of iOS 14.0.1, your own app will almost certainly crash shortly afterwards with a system stack trace that looks something like this:

Screenshot 2020-10-18 at 2.59.53 pm

The best way to stop this happening is to provide an empty completionHandler parameter:

poi.primaryButton = CPTextButton(
    title: "GO",
    textStyle: .normal,
    handler: { _ in 
        item.openInMaps(
            from: templateApplicationScene,
            completionHandler: { _ in return }
        ) 
    }
)
Enter fullscreen mode Exit fullscreen mode

And now the external maps app opens on your CarPlay display with your location prepopulated, but this time without any crashes on your app.


Note about the CarPlay external display on the iOS simulator: annoyingly there is no maps app on the CarPlay display on the simulator. Opening the link opens a "Now Playing" display, as shown below. This in turn explains why I have no further screenshots of the maps app experience from here.

Simulator Screen Shot - iPhone 11 Pro Max - 2020-10-18 at 15.04.28


Now I did mention MKMapItem.openInMaps(...) had a launchOptions parameter. Given that you already have a destination prepopulated and it's also a car routing, no parameters need passing here - therefore launchOptions can be left out.

I would however suggest populating 2x additional properties of your MKMapItem instance to give the maps app some extra information:

  • name - this shows up as the name of the location. Chances are you'll know this in some way. If you leave this out, the maps app will name the location "Unknown Location", which isn't desirable.
  • pointOfInterestCategory - this is more for future-proofing than a necessity if you have this information at hand - it might help the maps app hook into any additional functionality for this specific location.

Finally, you may be applying a button like this from a different kind of template, such as CPInformationTemplate - again, as long as you have the location that you wish to navigate to, creating a map item only requires an MKPlacemark instance, which only requires the location in question to initialise.

Discussion (4)

Collapse
ajeet2510 profile image
ajeet2510

Hi, Can I get the source code for this project, actually I am doing the same thing.

Collapse
simonrice profile image
Simon Rice Author • Edited

Sorry I've only just seen this.

I'll clobber something together & let you know when it's up (& probably update this post too) - it might just be a gist containing a sample class conforming to CPTemplateApplicationSceneDelegate rather than a full project - this is because of the whole situation around CarPlay provisioning not being readily available to everyone.

Collapse
ajeet2510 profile image
ajeet2510

Hey, I did my project with help of Apple source code. Thanks!!

Collapse
eduardo_paradapardo_c1eb profile image
eduardo parada pardo

Could you share your project example?