How I Used Swift to Build a Map Based Location Tracker for iOS

Anything location based is really cool, and having that information displayed on a map is even better! So … lets track our location and plot it on a map :)

images

The good news about this tutorial is there is very little code! The flip side is, we will have to do some extra setup. Lets begin!

Preface: I’m still very much a beginner to Swift, and I am just sharing my journey to help out other beginners :) I’m also using Xcode beta 5 for this.

Setup

Setting up the project

1
File -> New -> Project

Then select

1
iOS -> Application -> Single View Application

Give it a name and make sure we are using the language Swift :)
images

Open our Main.storyboard file.
images

Show the document outline. There is a little handle in the lower left window of the storyboard to do this.

images

Highlight our View Controller for this scene, and navigate to the attributes inspector.

images

For this project, we are going to set the size to be “Retina 4-Inch Full Screen” and I’m going to set the Orientation to Portrait.

Setting up the MapKit View

Apple has already provided us with a map view. So we just need to drag one into our scene.
images

Setting up the label

We are also going to have a label to display some debug information about our location. Drag one into the scene as well.
images
Feel free to adjust the attributes in the inspector to make the label look like mine. I centered the text, gave the label 3 lines, and used the awesomely cool font ‘Helvetica Neue Thin’ with a font size of 12 (incorrectly “17.0” in screen shot).

Creating the IBOutlets

Now that we have our mapview and label, we are going to drag them into our ViewController.swift code so we can control them with code.

We want to switch to assistant editor (the view that looks like a suit and bow tie in the upper right), and then “right click drag” our MKMapView into our ViewController.swift file.
images
images

We also want to right click, and drag from the Label to our ViewController.swift file.
images
images

You will see some errors, but this will go away once we fix our imports. If you are dying to fix this now, add this under import UIKit

1
2
import CoreLocation
import MapKit

Editing the info.plist

We need to add 2 keys to our info.plist file, which is located in the “Supporting Files” folder. The two keys are NSLocationWhenInUseUsageDescription and NSLocationAlwaysUsageDescription. Right click on “Information Property List”, click Add Row, and manually enter these two keys.

images

The final form should look like this:

images

Simulating your location

The last thing I want to mention is there is no way (I saw) to have the iOS simulator aware of your actual location. However, the simulator DOES allow you to fake this data. Make sure you have a good simulation selected or else this program will just show an empty map.

Once you have the simulator going select a location!!!
images

ViewController.swift and Full Code

Phew! Thank goodness all that setup is out of the way. Time for the code!

Inheritance & Initial variables

1
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {

Note that we have added CCLocationManagerDelegate and MKMapViewDelegate as classes we are inheriting from.

If you feel comfortable with the concept of inheritance, try holding the command key and clicking on MKMapViewDelegate or CCLocationManagerDelegate to get a list of methods that we can use.

If you are not comfortable, its not that important for this tutorial.

1
2
3
4
5
@IBOutlet weak var theMap: MKMapView!
@IBOutlet weak var theLabel: UILabel!

var manager:CLLocationManager!
var myLocations: [CLLocation] = []

I’ve already explain the purpose for the Interface Builder outlets is so that we can control the things we built in the interface with the code.

We create a manager of type CLLocationManger and since we are not giving it an initial value, we make it an optional with the ! mark.

Per Apple’s definition: “Optionals say either “there is a value, and it equals x” or “there isn’t a value at all”. Optionals are similar to using nil with pointers in Objective-C, but they work for any type, not just classes. Optionals are safer and more expressive than nil pointers in Objective-C and are at the heart of many of Swift’s most powerful features.”

We also set up an array of CLLocation’s and name that myLocations. This is where we are going to store every location we go to. I initialize it as an empty array, so no need for an optional here :)

viewDidLoad

For the sake of this app, this functions acts as our entry point to the entire program. Technically, this function is called when the view is done loading.

1
2
3
4
5
6
7
8
9
override func viewDidLoad() {
    super.viewDidLoad()
    
    //Setup our Location Manager
    manager = CLLocationManager()
    manager.delegate = self
    manager.desiredAccuracy = kCLLocationAccuracyBest
    manager.requestAlwaysAuthorization()
    manager.startUpdatingLocation()

This is where we initialize our CLLocationManager. Basically, this guy is responsible for knowing where we are at all times … physically. We use a delegate so we can take advantage of a function that gets called every time our location is updated:
locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject])

There are various desiredAccuracy’s we can use, but we want this accurate to within 5 meters, so we are using kCLLocationAccuracyBest.

When we call manager.requestAlwaysAuthorization you will get a little popup on your iOS device that will ask for permission so that the app will know our location. I encourage you to say yes to this ;)

Finally, we call manager.startUpdatingLocation() which actually starts checking if we change locations or not. If we change locations, it will call:
locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject])

1
2
3
4
5
    //Setup our Map View
    theMap.delegate = self
    theMap.mapType = MKMapType.Satellite
    theMap.showsUserLocation = true
}

We are setting up our MapKitView here. We are delegating ourself so we can later specify how to render our overlays. Overlays are anything that sits on top of the map. In our case, the lines we draw will be overlays.

Our map type is Satellite but you can also choose MKMapType.Standard and MKMapType.Hybrid much like you would the Maps app on our iPhones.

The variable showsUserLocation will add the blinking blue dot to our map to show where we currently are.

locationManager didUpdateLocation

As one might guess, this function gets called every time our location changes.

1
2
3
func locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject]) {
    theLabel.text = "\(locations[0])"
    myLocations.append(locations[0] as CLLocation)

The first thing we do is update theLabel’s text with the details about our location. This will show all the interesting details at the bottom of our app including our longitude, latitude, how fast we are moving, and details about what time it is.

Notice how locations is an array of CLLocation’s. In my experimentations, it seems like this array always only has 1 value. This is why I’m accessing the 0th (first) index of the array. I imagine this might have multiple values if we are tracking more than one object. If anyone would like to enlighten me, that would be great :)

We add this location to the array we created for holding all of our locations named myLocations.

1
2
3
4
    let spanX = 0.007
    let spanY = 0.007
    var newRegion = MKCoordinateRegion(center: theMap.userLocation.coordinate, span: MKCoordinateSpanMake(spanX, spanY))
    theMap.setRegion(newRegion, animated: true)

The map will display our location, but it will be zoomed out. If we want to zoom into our location, we need to set the map’s region. We are setting the center of the region as our current location via theMap.userLocation.coordinate. The span refers to how zoomed in we actually are in the region. Feel free to play around with these numbers. They felt right to me, shaken.. not stirred.

1
2
3
    if (myLocations.count > 1){
        var sourceIndex = myLocations.count - 1
        var destinationIndex = myLocations.count - 2

We only want to draw a line if we already have at least 2 locations saved to myLocations. I set up the index’s to use to make the code easier to read.

1
2
3
        let c1 = myLocations[sourceIndex].coordinate
        let c2 = myLocations[destinationIndex].coordinate
        var a = [c1, c2]

The coordinate method for CLLocation returns a CLLocationCoordinate2D. With this we can create a line between the two points. We are creating an array out of the points we want to draw lines between with my poorly named variable a.

1
2
3
4
        var polyline = MKPolyline(coordinates: &a, count: a.count)
        theMap.addOverlay(polyline)
    }
}

MKPolyline has some interesting syntax, but it basically needs a pointer to an array of CLLocationCoordinate2D’s which happens to be our array a. So I pass a reference to this pointer with the & symbol. Pointers and references are a big topic that I will not be digging into with this tutorial, but I encourage you to do some research on this topic.

Finally, we add our polyline to the theMap as an overlay.

We are not quite done yet, even tho we added the line to the map, the map has no idea how to actually draw it. We have to specify a renderer for it, which we will talk about next.

mapView rendererForOverlay

We are inheriting this method from our MKMapView delegation. This method will be called whenever the MKMapKitView has to render an overlay.

1
2
3
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
    
    if overlay is MKPolyline {

The first thing we do is check to see if the overlay is of type MKPolyline. If you are rendering things that are not polyline’s we use a different type of renderer.

1
        var polylineRenderer = MKPolylineRenderer(overlay: overlay)

We are creating a MKPolylineRenderer and passing out overlay to it. In our case, it is the polyline we created in the didUpdateLocation. There are different renderers for different things.

1
2
3
4
5
6
        polylineRenderer.strokeColor = UIColor.blueColor()
        polylineRenderer.lineWidth = 4
        return polylineRenderer
    }
    return nil
}

We are specifying the color of the line, and how wide it should be, and finally returning the polylineRender. In our case, if the overlay is not a MKPolyline we are returning nil.

Conclusion

Well, that was a lot of fun and I’m really happy with the results. I was watching that little car drive around the whole time I was writing this tutorial. I don’t know what sorta freeways let you go 30 mph, but whatever ;)

We can do a lot with this framework. One thing I was doing by accident at first was using Apple’s MKDirections API to ask for directions between the two locations to draw a line before I realized I didn’t have to do that at all. I actually had the code mostly working, but maybe I will save that for a tutorial some other day.

Anyhoo, hope this is useful and I hope you all enjoy learning Swift as much as I am :)

Comments