How to Build a Compass App in Swift 🧭

⚠️ This article assumes that you’re already somehow familiar with the Core Location Framework

Back in 2007 both Android and iOS (then iPhone OS) were at the very early stages of what has become the smartphone biggest revolution.

Along with the Android announcement, Google also launched its first Android Developer Challenge, aimed to raise developers interest to the platform.

Among the several entries to the challenge there was Enkin, essentially Google Maps on steroids. If you have 7 minutes to spare, here’s the video entry:

These guys didn’t win the challenge…they got hired by Google!

Bare in mind that, at the time, most of the world still didn’t know what Android or an iPhone were, we also didn’t have the App/Play Store yet!

After watching that video, my mind was completely blown away 🤯

Now let’s skip forward to 10 years later (today!), how difficult it is to create something similar? Well, replicating the whole Enkin concept might take too long, let’s start with the compass: how hard it is to create a Compass app?

The Basics: iPhone Heading

Nowadays every iOS device has a magnetometer on board, that plus iOS’s CLLocationManager makes this first challenge incredibly easy.

One of the functionalities of CLLocationManager is reporting the device Heading.

The Heading comes in the form of a CLHeading data object which, among its properties, contains trueHeading, that is the actual device orientation relative to true north, in degrees.

Just imagine a 2D vector with origin on your home button and pointing towards the earpiece speaker: the trueHeading is the angle between that vector and the vector starting from the same point but pointing towards the true north.

If your phone points exactly to the True North you’ll get a heading of 0 (zero) degrees, if it points towards East you’ll get 90 degrees etc.

The Picture

We need a picture of something pointing, you can choose anything you want, what it matter is that the North arrow points up, this is what I use:


Why it is important that it points up? Because this way, when we get a 0-degree (zero-degree) heading, we know that we don’t have to rotate the picture, as it is already pointing to the right direction!

If we get any other heading angle, then we know that we will need to rotate the picture exactly of the same degrees.

Code Snippets

These are the core parts for our first Compass App:

Location Manager

This is the CLLocationManager declaration, with the request to start monitoring the device heading embedded in.

let locationManager: CLLocationManager = {
  return $0

Location Manager Delegate

As I’ve said above, all we have to do is rotate our image of the same angle as our heading, I didn’t lie:

func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
  UIView.animate(withDuration: 0.5) {
    let angle = newHeading.trueHeading.toRadians // convert from degrees to radians
    self.imageView.transform = CGAffineTransform(rotationAngle: angle) // rotate the picture

CGAffineTransform requires a rotationAngle expressed in radians, therefore I’ve added a small extension to convert our heading angle to radians:

extension CGFloat {
  var toRadians: CGFloat { return self * .pi / 180 }
  var toDegrees: CGFloat { return self * 180 / .pi }

New in Swift 3.1: the π value is defined as a static property on Double, Float, and CGFloat!

That’s it! This is all you need to build your first compass app! 🎉

Pointing At Any Direction

Our first target is complete, we can build a whole Compass App in less than 50 lines of code!

One of the cool features about Enkin is that, given the location, the App can point at things around you.

Say we have a Movie Theater that we really, really, like. However, we get lost fairly often and we always miss the first five minutes of every movie. We want to put an end to that: how can we build an app that always points at our movie theater?

Obviously knowing only the device heading is not enough anymore, to solve this new challenge we must have three parameters: our device heading, our device location, and our movie theater location.

The Parameters

We’ve seen how to get the device heading and obviously we know by heart the GPS location of our beloved movie theater.

To also keep track of our location we will need to update our Location Manager declaration to request not only the device heading, but also our device location:

let locationManager: CLLocationManager = {
  $0.startUpdatingLocation() // now we request to monitor the device location!
  return $0

All it’s left to do now is to declare and fill in our delegate’s locationManager(_:, didUpdateLocations:) function, then we will have all our parameters!

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  guard let currentLocation = locations.last else { return }
  lastLocation = currentLocation // store this location somewhere

Rotate The (Compass) Picture

Now that have our three parameters, we need to rotate our picture accordingly to where the movie theater is.

In order to do that, first we must compute the Bearing angle between our location and the Movie Theater.

In this case saying “A picture is worth a thousand words” really shines, this is what the Bearing between two locations is:

Note: β is our Bearing


Picture shamelessly stolen from StackOverflow.

If your phone points exactly to the true north, the Bearing is the angle that your device must rotate in order to point right to our movie theater.

In order to compute our bearing, we must project our GPS locations (the device’s and the theater's) into a 2D plane, and then apply the formula above.

There’s no definitive way to map each GPS location to a 2D panel point, this is why we have so many different world map projections. Luckily, in our case any projection will do.

There are plenty of solutions for the bearing problem on StackOverflow, I just pick one and went on with the rest of the project:

public extension CLLocation {
  func bearingToLocationRadian(_ destinationLocation: CLLocation) -> CGFloat {
    let lat1 = self.coordinate.latitude.degreesToRadians
    let lon1 = self.coordinate.longitude.degreesToRadians
    let lat2 = destinationLocation.coordinate.latitude.degreesToRadians
    let lon2 = destinationLocation.coordinate.longitude.degreesToRadians
    let dLon = lon2 - lon1
    let y = sin(dLon) * cos(lat2)
    let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
    let radiansBearing = atan2(y, x)
    return CGFloat(radiansBearing)
  func bearingToLocationDegrees(destinationLocation: CLLocation) -> CGFloat {
    return bearingToLocationRadian(destinationLocation).radiansToDegrees

This piece of code takes care of both the projection and the bearing computation. Credits to Fabrizio Bartolomucci.

We can now finally update our image rotation with the device latest heading and bearing:

UIView.animate(withDuration: 0.5) {
  self.imageView.transform = CGAffineTransform(rotationAngle: latestBearing - latestHeading)

That’s it! No more arriving late to the movies! 😆

Code Snippet

We’ve seen quite a bit in this post and, because of this, I’ve decided to open source the whole Compass app.


Click on the picture to jump to the repository.

Once launched, the app will point you towards the North, you can tap anywhere and a World map will be shown: tap wherever you’d like and the compass will start pointing at that new location.

Beside what we’ve seen in this post, the app also handles interface rotation (landscape left/right, portrait ..) and device rotation (a.k.a. device upside down etc). Check out the source code!

The app is also available on the App Store (because..why not?), completely free of charge.

That’s it for today, happy coding!