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.
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.
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
From here, it’s pretty straightfoward to “flesh out” the two schemes.
The code speaks for itself, but ideally the
url property will be constructed with either a location name:
Or the coordinates:
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.
The Waze scheme is very similar to Google Maps, although the annotation’s title will be URL escaped instead of using +’s.
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
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.
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).
In this particular case, we are using an
MKMapView to trigger our map app options, but you could very well do it with a
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!