Open Map Location in Multiple Apps from MKMapView
You’ve probably seen in a lot of apps where tapping on a map location or address gives you the option to open that location in one of the map applications installed on your phone. Perhaps you tap on the address for an event in the Facebook app, and it gives you the option to see that location in Apple Maps, Google Maps, or Waze, depending on what apps you have on your phone. I wanted to attach this functionality to an MKMapView
in an app I’m currently working on
in a clean, encapsulated way, and thought I would share that in a post. I took what I had learned from a few other great articles, and combined them to fit my needs.
Requirements
To get started, I had to decide what map applications I wanted to support. Everyone will obviously have Apple Maps on their phone, but may also optionally have Google Maps or Waze as well. I decided to stick with these “big three” and go from there. Next, if there was only one app available on the phone (e.g. Apple Maps), I didn’t want to show a list with only one option, so tapping on the MKMapView
would skip the selection step and directly open in Apple Maps. The final thing to keep in mind for my situation was that the actualy map view was located in a child view controller of the currently displayed top-level controller, so I wanted to offer the map selection pop-up in the parent view controller, even though the prompt would be triggered from the child view controller.
Initial Setup
The first thing we need to do is to “whitelist” Google Maps and Waze in the Info.plist configuration file. Create an array entry with the key of LSApplicationQueriesSchemes. This will allow the app to determine if Waze and Google Maps are installed and available to be used on the device. From the Apple docs:
LSApplicationQueriesSchemes (Array - iOS) Specifies the URL schemes you want the app to be able to use with the canOpenURL: method of the UIApplication class. For each URL scheme you want your app to use with the canOpenURL: method, add it as a string in this array.
These URL schemes, unique to each app, allow for inter-application communcation, and you can read more about them here. For our two map applications, we’ll add the array entries comgooglemaps and waze.
Building Scheme Info
Since Apple Maps already has a nice built-in API for opening itself from another iOS application with a particular location and configuration, I wanted to construct a similar, encapsulating interface for the other third-party map schemes as well. For Google Maps and Waze, we need to build out:
- A mechanism to determine if each app is available to use on the device
- The actual URL for opening the app with the location information
I began this process with a simple protocol, aptly named MapAppScheme
:
From here, it’s pretty straightfoward to “flesh out” the two schemes.
Google Maps
The code speaks for itself, but ideally the url
property will be constructed with either a location name:
comgooglemaps://?saddr=&daddr=Billy+Goat+Hill¢er=37.7415,122.4330
Or the coordinates:
comgooglemaps://?saddr=&daddr=37.7415,122.4330¢er=37.7415,122.4330
The way the URL is constructed will enter the location as the destination for traveling directions, with the starting location left blank. You can learn more about the Google Maps URL scheme here.
Waze
The Waze scheme is very similar to Google Maps, although the annotation’s title will be URL escaped instead of using +’s.
https://waze.com/ul?q=66%20Billy%20Goat%20Hill&ll=37.7415,122.4330
Additionally, since the Waze URL scheme is a more traditional URL, I built it using the URLComponent
class instead of interpolating the results into the URL string directly. You can learn more about the Waze URL scheme here.
Consolidating the Results
Now that the schemes are built out, I constructed a helper class to determine which of the two I could use.
We first check if the scheme is available by using the canOpenURL
function, then add our constructed URL to the list of availableSchemes
. I then created a MapActionSheetViewController
, which is a UIAlertController
action sheet.
There’s a lot going on here, but hopefully the code with comments will make clear what’s going on. Basically as we add options to the action sheet, we associate the logic for opening each map application with the correct URL (or executing a closure in the case of Apple Maps) to that option. As we wrap up, hopefully this will all make sense. However, for a visual representation, this will render the highlighted section below.
One final thing to note about the renderAppleMaps
closure; we are passing this functionality in rather than declaring it inside the controller since we might have the case where the only map app available is Apple Maps. Therefore, rather than writing the code to open the location in Apple Maps in two places in the codebase, we can write it in one place and pass it in if we need to, or just execute it when the MKMapView
is tapped.
Wiring Up the View
As mentioned at the beginning of the article, the MKMapView
is actually inside the view of a child view controller, and we want to render our UIAlertController
action sheet in the parent controller. The child view controller is called ParkDetailsMapViewController
, and the parent controller is the ParkViewController
.
We can accomplish this by setting up a UITapGestureRecognizer
on the MKMapView
, then setting the ParkViewController
parent as the delegate to a protocol declared in the child ParkDetailsMapViewController
. When the MKMapView
is tapped, trigger the delegate to show the action sheet. First, the ParkDetailsMapViewController
, in which I’m only showing the relevant parts.
I added the openInMaps
function as a private extension to the controller.
If you notice, the two arguments we’re sending to the renderMapSelectionActionSheet
delegate function happen to match the arguments required for the initialization of the MapActionSheetViewController
. You can probably guess where we’ll be creating that controller instance….
Before we jump over to the parent ParkViewController
, I want to point out that the openInMaps
function on the mapView
variable is actually an extension attached to the MKMapView
for opening the location in Apple Maps.
Hopefully the code and comments are enough of an explanation, but feel free to play around with different values here to see what kind of results you get in the map view.
Finishing Touches
The last piece is one of the most straightforward, where we actually build out the renderMapSelectionActionSheet
delegate function. At this point, all we need to do is take the arguments passed in, instantiate the MapActionSheetViewController
with those arguments, and present the view controller.
And voilà! Our finished product (sorry for the heavy duty GIF).
Conclusion
In this particular case, we are using an MKMapView
to trigger our map app options, but you could very well do it with a UITableViewCell
or UILabel
location address as well. This is one way I felt worked best for this particular codebase, but there are many other great tutorials on how to do this out scattered about the interwebs. I hope you’ve been able to pick up a few good ideas from this lesson on your iOS journey. Thanks for reading!