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:
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
)
The template will now show one location...
...and you can drill down to the location in question...
...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
)
}
)
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:
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 }
)
}
)
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.
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.
Top comments (4)
Hi, Can I get the source code for this project, actually I am doing the same thing.
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.Hey, I did my project with help of Apple source code. Thanks!!
Could you share your project example?