Top

Implement location tracking using MapKit in Swift

Location based services have always looked like a very cool feature to me. Showing the user their covered or targeted location and route made the process of moving around so much better. In this blog, we will try to implement MapKit, a very simple and easy framework provided by Apple, to work upon with map and location requirements. Our aim will be to implement location tracking services and show the covered route on Map View. This will be done using Swift.

mapkit_practice

 

Create Xcode project

To start off, open Xcode, and select the option Create a new Xcode project. From there, select iOS->Application->Single View Application, and click on Next. After entering your project name choose language as Swift.

To work with location services and MapKit, you will have to add MapKit and CoreLocation frameworks from Project Settings->Build Phases->Link binary with Libraries.

Now go to file ViewController.swift and add these two lines to import MapKit and CoreLocation framework through the view controller.

Initial Setup

Now go to storyboard file and add map view to your view controller, present in Object library. Create a property of your mapview on view controller file and name it mapView.

Assign your view Controller to CLLocationManagerDelegate and MKMapViewDelegate.

For authorizing app to location services, add NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription keys to your project’s Info.plist. Add the following string against your authorization keys, “Your Project wants to use your present location”.

Time to start Coding…!! 🙂

First create a property of CLLocationManager by adding the following line:

After that lets do some setup for locationManager to get it working. Add the following lines of code in func viewDidLoad()

To complete the authorization process for enabling location services, add the following lines of code

Now, run your project. As soon as your application launches, you will see an authorization alert on the application screen. Allowing that will enable the application to get location of user.

But where to get that location from???

In order to get the user’s current location, implement the CLLocationManager delegate:

Then, add the following lines to print the obtained user location

Now run your project and you will see logs for changing location of user.

NOTE : If you are not getting any location, click on the Simulator and select Debug->Location->City Bicycle Ride. This will produce a simulated route for a location. The route calculation is done by Apple.

Map Setup

So, how about displaying live user location on the Map?

To get the visible user location from the map, add the following lines in func viewDidLoad()

Now run your project and you will see the user location change in the map.

Showing Route On Map

Now we will show the route covered by a user.
To do that, add the following lines in CLLocationManager delegate

func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!)

But that isn’t enough. The above code will just add overlay as route on map. But to render overlay on the map, another MKMapView delegate func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! is required.

Add the following lines inside the delegate mentioned above

Run the project and you will be able to see a trail of red colour, behind the user location i.e. the route.

Finish off by adding annotations to the route

To add annotations, add the following function that takes CLLocation as argument and draws annotation at required location. It is done after getting the location name from the coordinates through CLGeocoder and applying it to title of annotation.

Now when you’re calling this function directly from CLLocationManager delegate, it will add annotations on the map. But by doing so it makes the map clusterized.

To solve this problem we will add the annotations every time we cover a distance of 200 metres.

For that remove the call to the function func addAnnotationsOnMap(locationToPoint : CLLocation) and add the following lines in CLLocationManager delegate.

As soon as you add these lines you will get an error for unknown variable previousLocation. To solve the issue add the following property

Now run your project and you can see the route as well as annotations (not clusterized), along with user location, being tracked on map.

Thats all folks!

To find a sample project of the above tutorial, check GitHub.

I hope the blog was simple and help you learn MapKit better. Feel free to comment in case of queries and any other advice.

Manish Kumar

Manish Kumar is an iOS developer at Kolkata, who enjoys building innovative applications. Besides he also loves playing football and listening to songs. He loves challenges and sharing his work in open source community. Lots of iOS objective C development work is going on in Innofied. You can reach us at contact@innofied.com

  • Pingback: Implement location tracking using MapKit in Swift | Dinesh Ram Kali.()

  • Daniel Hjärtat Hjärtström

    Hello Manish!

    First, thank you for this tutorial!

    Second, I am having som problems with the code and was wondering if you could help me. The problem I am having is; When the app starts, it finds the user location, however, there is a Polyline drawn starting in the ocean outside Africa (0,0). How can I make it so that the line will start at the simulated location?

    Best regards,

    Daniel

    • Manish Kumar

      Hello Daniel. Thanks for appreciating. Are you running application in simulator? If yes, just click on the simulator and then from Debug, you can find Location options through which you can simulate.

      • Daniel Hjärtat Hjärtström

        That is what I am doing, but the polyline still starts at 0,0. Maybe this is a small bug in Xcode, no matter how I try, the first coordinates when printed are always 0,0 =/

        Best regards,

        Daniel

        • Manish Kumar

          Did you try using custom location?

          • Daniel Hjärtat Hjärtström

            Yeah, But still the same problem, when I print the coordinates the first one is always 0,0 =/

          • Manish Kumar

            Hello Daniel, it would be better in understanding the issue if I can see some of your code as I am not able to figure out the exact issue.

          • Daniel Hjärtat Hjärtström

            I managed to solve the problem by checking if the coordinates latitude differed from 0.0 before calling the function to create the polyline. I believe that this must be a problem with the simulator and not the code itself (since even with the simulated position, the first coordinates returned are (0,0) ). I will try to test it on a device in the near future 🙂 Thamk you for your responses!

          • Maneesh sharma

            Hello Daniel , can you please tell me what you did in your code for checking if coordinates latitude differed from 0.0 before calling the function?

          • Daniel Hjärtat Hjärtström

            […]
            let oldCoordinates = oldLocation.coordinate
            let newCoordinates = newLocation.coordinate

            if oldCoordinates.latitude != 0 {

            if let oldLocationNew = oldLocation as CLLocation? {
            var area = [oldCoordinates, newCoordinates]
            var polyLine = MKPolyline(coordinates: &area, count: area.count)
            mapView.addOverlay(polyLine)
            }

            […]

  • James Hartley

    I am having the same problem, Daniel. What was the solution you found?

  • Kevin Maynard

    Awesome tutorial! Now how would I save the final map (with annotations & polyline directions to an image) in Swift? I can use snapshotter to create the map image and add the annotations, but I haven’t figured out how to draw the polyline onto the map.

    • Manish Kumar

      Do you want to take a screenshot of the application?

      • Kevin Maynard

        Kinda of… I want to create a PNG image of the final map (with the route and pins). It will be ultimately saved to a website and then retrieved by the user at a later date. Does this make sense? I used snapshotter to create the basic map image and was able to get the start and end pins to be added. What I’m looking for is how do I draw my direction route onto the image? My code so far is:

        map.sizeToFit()
        map.showsUserLocation = false
        takeSnapshot(map) { (image, error) -> () in
        guard image != nil else {
        print(error)
        return
        }

        if let imageData = UIImagePNGRepresentation(image!) {
        let mapDirections = MKDirectionsRequest()
        let origin = self.textfieldFrom?.text
        let destination = self.textfieldTo?.text

        if (origin?.isEmpty==true || destination?.isEmpty==true) {
        print(“No value set for map”)
        } else {
        self.mapManager.directionsUsingGoogle(from: origin!, to: destination!) { (route,directionInformation, boundingRegion, error) -> () in

        if(error != nil){
        print(error)
        } else{
        let pointOfOrigin = MKPointAnnotation()
        pointOfOrigin.coordinate = route!.coordinate
        pointOfOrigin.title = directionInformation?.objectForKey(“start_address”) as! NSString as String
        pointOfOrigin.subtitle = directionInformation?.objectForKey(“duration”) as! NSString as String
        let pointOfDestination = MKPointAnnotation()
        pointOfDestination.coordinate = route!.coordinate
        pointOfDestination.title = directionInformation?.objectForKey(“end_address”) as! NSString as String
        pointOfDestination.subtitle = directionInformation?.objectForKey(“distance”) as! NSString as String

        let start_location = directionInformation?.objectForKey(“start_location”) as! NSDictionary
        let originLat = start_location.objectForKey(“lat”)?.doubleValue
        let originLng = start_location.objectForKey(“lng”)?.doubleValue

        let end_location = directionInformation?.objectForKey(“end_location”) as! NSDictionary
        let destLat = end_location.objectForKey(“lat”)?.doubleValue
        let destLng = end_location.objectForKey(“lng”)?.doubleValue

        let coordOrigin = CLLocationCoordinate2D(latitude: originLat!, longitude: originLng!)
        let coordDesitination = CLLocationCoordinate2D(latitude: destLat!, longitude: destLng!)

        pointOfOrigin.coordinate = coordOrigin
        pointOfDestination.coordinate = coordDesitination

        let coordsStart = CLLocationCoordinate2DMake(coordOrigin.latitude, coordOrigin.longitude)
        let placeStart = MKPlacemark(coordinate: coordsStart, addressDictionary: nil)
        print(placeStart)

        let coordsEnd = CLLocationCoordinate2DMake(coordDesitination.latitude, coordDesitination.longitude)
        let placeEnd = MKPlacemark(coordinate: coordsEnd, addressDictionary: nil)
        print(placeEnd)

        mapDirections.source = MKMapItem(placemark: placeStart)
        mapDirections.destination = MKMapItem(placemark: placeEnd)
        print(mapDirections)

        let directions = MKDirections(request: mapDirections)
        directions.calculateDirectionsWithCompletionHandler { response, error in
        guard let response = response else {
        print(“Directions error: (error)”)
        return
        }

        /* Everything works up to this point */
        /* No errors, but the polyline isn’t drawn to the image */
        self.stepImagesFromDirectionsResponse(response) { stepImages in
        stepImages.first
        }
        }
        }
        }
        }

        The image below is the result. As you can see, everything is there except the directional route.

        • Manish Kumar

          Hi Kevin, So you want to tell that, instead of drawing route on map, you want to draw route on the snapshot image? Am I right?

          Also what is snapshotter? You can view this link to take snap shot of the screen : http://stackoverflow.com/questions/2214957/how-do-i-take-a-screen-shot-of-a-uiview/18925301#18925301

          I understood your requirement a bit. What you can do is first create pins and draw route between points on map. And then take screenshot of the application.

          • Manish Kumar

            For drawing route on map do the follwing :

            1.. get old and new location as start point and end point.

            2. Implement delegate func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! and add the following code inside it:

  • J.D. Lace

    mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer is the new delegate method in Swift 2.2. Can you update the tutorial to reflect the new solution? Thanks.

  • Guest

    Is there a way to add multiple users?

  • Museer

    Let us suppose i am getting multiple user location from webservice every 5 seconds then how can we track him?.

  • Karthik Reddy

    i want to calculate the distance from 1 point annotation to another point. help me

  • skinysmn

    Hi im trying to make your code working in current version xcode but it doesn’t show route anв do not show placemarks