Kodeco Forums

Routing with MapKit and Core Location

In this tutorial, you'll learn how to calculate the quickest route between two or three given locations using a mashup of MapKit and Core Location.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/1468-routing-with-mapkit-and-core-location

Hello, can you fix the link for the complete project code. Thanks you.

I just checked the link and incremented the file name to

https://www.raywenderlich.com/wp-content/uploads/2015/11/ProcrastinatorsRevenge-2.zip

Hope this helps

Great article on an interesting topic.

I do have one question however. Why did you add extensions rather than functions when you have the source code?

Hi Lindsey, Thank you this is amazing. I have a really dumb question!!! Where do you create the code making this a return trip?
Also, is there anyway to create a list of roads (as opposed to directions)?
Thank you

I found our how to take the “roundtrip” part of the code away - thank you.

If you have any time I am interested in trying to list the roads in the route (as apart from the step by step directions). Is that possible

Hi, can i change the language of root instructions? i want that the instructions in italian language thanks

Thanks for a great and informative post. I have a question. I have been trying to figure out how to display a label similar to the time label, but for the overall distance of the route, but have been unsuccessful so far. Any ideas or pointers?

THANKS!

Finally Solved it. Thanks again

Hi Lindsey, Do you have any hints on how to convert the code to Swift 3.

I am getting one remaining error and don’t have the knowledge to fix it

The working Swift 2 Code Block was

func showRoute(routes: [MKRoute], time: NSTimeInterval) {
var directionsArray = (startingAddress: String, endingAddress: String, route: MKRoute)
for i in 0…<routes.count {
plotPolyline(routes[i])
directionsArray += [(locationArray[i].textField.text!,
locationArray[i+1].textField.text!, routes[i])]
}
displayDirections(directionsArray)
printTimeToLabel(time)
}

Swift 3 has converted this to

func showRoute(routes: [MKRoute], time: TimeInterval) {
var directionsArray = (startingAddress: String, endingAddress: String, route: MKRoute)
for i in 0…<routes.count {
plotPolyline(route: routes[i])
directionsArray += [(locationArray[i].textField?.text,
locationArray[i+1].textField?.text, routes[i])]
}
displayDirections(directionsArray: directionsArray)
printTimeToLabel(time: time)
}
This produces an error on the line

directionsArray += [(locationArray[i].textField?.text,
locationArray[i+1].textField?.text, routes[i])]
Cannot convert value of type ‘[(startingAddress: String, endingAddress: String, route: MKRoute)]’ to expected argument type ‘inout _’

I saw your question but There is no one answer. And, That is not only one problem.

Here is full body of ViewController

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController {
    
    @IBOutlet weak var sourceField: UITextField!
    @IBOutlet weak var destinationField1: UITextField!
    @IBOutlet weak var destinationField2: UITextField!
    @IBOutlet weak var topMarginConstraint: NSLayoutConstraint!
    @IBOutlet var enterButtonArray: [UIButton]!
    
    var originalTopMargin: CGFloat!
    
    let locationManager = CLLocationManager()
    var locationTuples: [(textField: UITextField?, mapItem: MKMapItem?)]!
    
    var locationsArray: [(textField: UITextField?, mapItem: MKMapItem?)] {
        var filtered = locationTuples.filter({ $0.mapItem != nil })
        filtered += [filtered.first!]
        return filtered
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        originalTopMargin = topMarginConstraint.constant
        
        locationTuples = [(sourceField, nil), (destinationField1, nil), (destinationField2, nil)]
        
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        
        if CLLocationManager.locationServicesEnabled() {
            locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
            locationManager.requestLocation()
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        navigationController?.isNavigationBarHidden = true
    }
    
    @IBAction func getDirections(_ sender: AnyObject) {
        view.endEditing(true)
        performSegue(withIdentifier: "show_directions", sender: self)
    }
    
    override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
        if locationTuples[0].mapItem == nil ||
            (locationTuples[1].mapItem == nil && locationTuples[2].mapItem == nil) {
            showAlert("Please enter a valid starting point and at least one destination.")
            return false
        } else {
            return true
        }
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let directionsViewController = segue.destination as! DirectionsViewController
        directionsViewController.locationArray = locationsArray
    }
    
    @IBAction func addressEntered(_ sender: UIButton) {
        view.endEditing(true)
        let currentTextField = locationTuples[sender.tag-1].textField
        CLGeocoder().geocodeAddressString(currentTextField!.text!,
                                          completionHandler: {(placemarks, error) -> Void in
                                            if let placemarks = placemarks {
                                                var addresses = [String]()
                                                for placemark in placemarks {
                                                    addresses.append(self.formatAddressFromPlacemark(placemark))
                                                }
                                                self.showAddressTable(addresses, textField:currentTextField!,
                                                                      placemarks:placemarks, sender:sender)
                                            } else {
                                                self.showAlert("Address not found.")
                                            }
                                            } )
    }
    
    func showAddressTable(_ addresses: [String], textField: UITextField,
                          placemarks: [CLPlacemark], sender: UIButton) {
        let addressTableView = AddressTableView(frame: UIScreen.main.bounds, style: UITableViewStyle.plain)
        addressTableView.addresses = addresses
        addressTableView.currentTextField = textField
        addressTableView.placemarkArray = placemarks
        addressTableView.mainViewController = self
        addressTableView.sender = sender
        addressTableView.delegate = addressTableView
        addressTableView.dataSource = addressTableView
        view.addSubview(addressTableView)
    }
    
    func formatAddressFromPlacemark(_ placemark: CLPlacemark) -> String {
        return (placemark.addressDictionary!["FormattedAddressLines"] as! [String]).joined(separator: ", ")
    }
    
    @IBAction func swapFields(_ sender: AnyObject) {
        swap(&destinationField1.text, &destinationField2.text)
        swap(&locationTuples[1].mapItem, &locationTuples[2].mapItem)
        swap(&self.enterButtonArray.filter{$0.tag == 2}.first!.isSelected, &self.enterButtonArray.filter{$0.tag == 3}.first!.isSelected)
    }
    
    func showAlert(_ alertString: String) {
        let alert = UIAlertController(title: nil, message: alertString, preferredStyle: .alert)
        let okButton = UIAlertAction(title: "OK",
                                     style: .cancel) { (alert) -> Void in
        }
        alert.addAction(okButton)
        present(alert, animated: true, completion: nil)
    }
    
    // The remaining methods handle the keyboard resignation/
    // move the view so that the first responders aren't hidden
    
    func moveViewUp() {
        if topMarginConstraint.constant != originalTopMargin {
            return
        }
        
        topMarginConstraint.constant -= 165
        UIView.animate(withDuration: 0.3, animations: { () -> Void in
            self.view.layoutIfNeeded()
        })
    }
    
    func moveViewDown() {
        if topMarginConstraint.constant == originalTopMargin {
            return
        }
        
        topMarginConstraint.constant = originalTopMargin
        UIView.animate(withDuration: 0.3, animations: { () -> Void in
            self.view.layoutIfNeeded()
        })
    }
}

extension ViewController: UITextFieldDelegate {
    
    func textField(_ textField: UITextField,
                   shouldChangeCharactersIn range: NSRange,
                   replacementString string: String) -> Bool {
        
        enterButtonArray.filter{$0.tag == textField.tag}.first!.isSelected = false
        locationTuples[textField.tag-1].mapItem = nil
        return true
    }
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        moveViewUp()
    }
    
    func textFieldDidEndEditing(_ textField: UITextField) {
        moveViewDown()
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        view.endEditing(true)
        moveViewDown()
        return true
    }
}

extension ViewController: CLLocationManagerDelegate {
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        CLGeocoder().reverseGeocodeLocation(locations.last!,
                                            completionHandler: {(placemarks, error) -> Void in
                                                if let placemarks = placemarks {
                                                    let placemark = placemarks[0]
                                                    self.locationTuples[0].mapItem = MKMapItem(placemark:
                                                        MKPlacemark(coordinate: placemark.location!.coordinate,
                                                                    addressDictionary: placemark.addressDictionary as! [String:AnyObject]?))
                                                    self.sourceField.text = self.formatAddressFromPlacemark(placemark)
                                                    self.enterButtonArray.filter{$0.tag == 1}.first!.isSelected = true
                                                }
        })
    }
    
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }
}

The following managed to build for me.

func showRoute(routes: [MKRoute], time: TimeInterval) {
        var directionsArray = [(startingAddress: String, endingAddress: String, route: MKRoute)]()
        for i in 0..<routes.count {
            plotPolyline(route: routes[i])
            directionsArray = [(startingAddress:(locationArray[i].textField!.text!),
                                 endingAddress: (locationArray[i+1].textField!.text!), route: routes[i])]

But I am receiving a Thread 1: signal SIGABRT error from AppDelegate

i think it’s for

CLGeocoder().reverseGeocodeLocation(locations.last!, 
                                  completionHandler: 
         {(placemarks:[CLPlacemark]?, error:Error?) -> Void in })

just need to change NSError to Error

still crashes with a EXC_BAD_INSTRUCTION (code=EXC_i386_INVOP, subcode=0x0) in simulator even after applying all the previous changes I’m run-on iOS 10.2 and Xcode 8.2.1

Here’s what I did to fix the error Cannot convert value of type ‘[(startingAddress: String, endingAddress: String, route: MKRoute)]’ to expected argument type ‘inout _’

func showRoute(routes:[MKRoute]) {
var directionsArray = (startingAddress: String, endingAddress: String, route: MKRoute)
var directionsArrayVar = (startingAddress: String, endingAddress: String, route: MKRoute)
for i in 0…<routes.count {
plotPolyline(route: routes[i])
directionsArrayVar = [((locationArray[i].textField?.text!)!, endingAddress: (locationArray[i+1].textField?.text!)!, route: routes[i])]
directionsArray.append(contentsOf: directionsArrayVar)
}
displayDirections(directionsArray: directionsArray)
}

I know it’s not the best option but it worked for me

This tutorial is more than six months old so questions are no longer supported at the moment for it. We will update it as soon as possible. Thank you! :]