Open an Alert asking to choose App to open map with

Swift 5+

Base on @Emptyless answer.

to

import MapKit

func openMapButtonAction() {
        let latitude = 45.5088
        let longitude = -73.554

        let appleURL = "http://maps.apple.com/?daddr=\(latitude),\(longitude)"
        let googleURL = "comgooglemaps://?daddr=\(latitude),\(longitude)&directionsmode=driving"
        let wazeURL = "waze://?ll=\(latitude),\(longitude)&navigate=false"

        let googleItem = ("Google Map", URL(string:googleURL)!)
        let wazeItem = ("Waze", URL(string:wazeURL)!)
        var installedNavigationApps = [("Apple Maps", URL(string:appleURL)!)]

        if UIApplication.shared.canOpenURL(googleItem.1) {
            installedNavigationApps.append(googleItem)
        }

        if UIApplication.shared.canOpenURL(wazeItem.1) {
            installedNavigationApps.append(wazeItem)
        }

        let alert = UIAlertController(title: "Selection", message: "Select Navigation App", preferredStyle: .actionSheet)
        for app in installedNavigationApps {
            let button = UIAlertAction(title: app.0, style: .default, handler: { _ in
                UIApplication.shared.open(app.1, options: [:], completionHandler: nil)
            })
            alert.addAction(button)
        }
        let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        alert.addAction(cancel)
        present(alert, animated: true)
    }

Also put these in your info.plist:

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>googlechromes</string>
    <string>comgooglemaps</string>
    <string>waze</string>
</array>

Cheers!


Swift 5+ solution based on previous answers, this one shows a selector between Apple Maps, Google Maps, Waze and City Mapper. It also allows for some optional location title (for those apps that support it) and presents the alert only if there are more than 1 option (it opens automatically if only 1, or does nothing if none).

func openMaps(latitude: Double, longitude: Double, title: String?) {
    let application = UIApplication.shared
    let coordinate = "\(latitude),\(longitude)"
    let encodedTitle = title?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
    let handlers = [
        ("Apple Maps", "http://maps.apple.com/?q=\(encodedTitle)&ll=\(coordinate)"),
        ("Google Maps", "comgooglemaps://?q=\(coordinate)"),
        ("Waze", "waze://?ll=\(coordinate)"),
        ("Citymapper", "citymapper://directions?endcoord=\(coordinate)&endname=\(encodedTitle)")
    ]
        .compactMap { (name, address) in URL(string: address).map { (name, $0) } }
        .filter { (_, url) in application.canOpenURL(url) }

    guard handlers.count > 1 else {
        if let (_, url) = handlers.first {
            application.open(url, options: [:])
        }
        return
    }
    let alert = UIAlertController(title: R.string.localizable.select_map_app(), message: nil, preferredStyle: .actionSheet)
    handlers.forEach { (name, url) in
        alert.addAction(UIAlertAction(title: name, style: .default) { _ in
            application.open(url, options: [:])
        })
    }
    alert.addAction(UIAlertAction(title: R.string.localizable.cancel(), style: .cancel, handler: nil))
    contextProvider.currentViewController.present(alert, animated: true, completion: nil)
}

Note this solution uses R.swift for string localization but you can replace those with NSLocalizedString normally, and it uses a contextProvider.currentViewController to get the presented UIViewController, but you can replace it with self if you are calling this in a view controller already.

As usual, you need to also add the following to your app Info.plist:

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>citymapper</string>
    <string>comgooglemaps</string>
    <string>waze</string>
</array>

You can create an array of checks to map the installed apps using sumesh's answer [1]:

var installedNavigationApps : [String] = ["Apple Maps"] // Apple Maps is always installed

and with every navigation app you can think of:

if (UIApplication.sharedApplication().canOpenURL(url: NSURL)) {
        self.installedNavigationApps.append(url)
} else {
        // do nothing
}

Common navigation apps are:

  • Google Maps - NSURL(string:"comgooglemaps://")
  • Waze - NSURL(string:"waze://")
  • Navigon - NSURL(string:"navigon://")
  • TomTom - NSURL(string:"tomtomhome://")

A lot more can be found at: http://wiki.akosma.com/IPhone_URL_Schemes

After you created your list of installed navigation apps you can present an UIAlertController:

let alert = UIAlertController(title: "Selection", message: "Select Navigation App", preferredStyle: .ActionSheet)
for app in self.installNavigationApps {
    let button = UIAlertAction(title: app, style: .Default, handler: nil)
    alert.addAction(button)
}
self.presentViewController(alert, animated: true, completion: nil)

Of course you need to add the behavior of a button click in the handler with the specified urlscheme. For example if Google Maps is clicked use something like this:

UIApplication.sharedApplication().openURL(NSURL(string:
            "comgooglemaps://?saddr=&daddr=\(place.latitude),\(place.longitude)&directionsmode=driving")!) // Also from sumesh's answer

With only Apple Maps and Google Maps installed this will yield something like this:

enter image description here