Build a Compass app with SwiftUI

Darren
Darren
Oct 10, 2019 · 12 min read
Image for post
Image for post
Thanks to drawkit.io for the nice illustration

Building this simple compass app took much longer than it should have. The actual SwiftUI code is around 100 lines, but even so, I definitely had some frustrating moments.

This post is mostly about the SwiftUI side of the compass, and not the actual compass logic(CoreLocation). The main part of this tutorial will be getting the rotations and padding correct, things like theText views always need to be readable even when the compass is turning and then spacing out each of the degrees Views.

This is what the final app will look like:

Image for post
Image for post

But enough of that, let’s see what the code looks like.

Step 1: Create the ContentView

In your ContentView replace the Text view with a VStack, this will be our base for everything else.

Next, we are going to add a Capsule view. This Capsule view will be used to show which direction you are facing. This is not really a required view, but I saw that the Apple compass had something similar :)

If you build and run the app now, your app will look like this:

Image for post
Image for post

Great, we have our app running, now we can get into the more tricky part of the code.

Update the ContentView

Replace your ContentView with the following:

In the above code, we added a couple of things. We added a ZStack with some styling as well as a ForEach that will display the CompassViewMarkers.

  1. We are using the ZStack because we want to put all the “compass marker views” on top of one another, this will allow us to rotate each one of those “compass marker views” based on the values that we pass to the ForEach. If we used a VStack or an HStack the markers would be laid out incorrectly. This will make more sense later in this article when we create and add the CompassMarkerView.
  2. We are working with a ForEach because we need multiple “compass marker views”. There are 12 values in the array for the ForEach, this represents each mark on the compass in degrees.
  3. .rotationEffect, this will rotate the ZStack when we get info from the compass logic later on. For now, we will set it to 0 so that we can finish the rest of the layout. Once the layout is done, adding the compass logic will be quick and easy.

If you are confused about what a “compass marker view” is, don’t worry, we will be creating that in Step 3.

Step 2: Creating and updating the compass data

In the previous step, we had a simple array that contained the degrees for each “compass marker view”. We will now create our data model and method that will return an array which will contain all the data we need.

To do this we will create a struct called Marker. The Marker will have two properties, degrees and label. We will also create a custom init so that we only need to add the label text to Markers that require it. We will also need to make the Marker Hashable so that we can use it in the ForEach. Luckily this is really simple as everything in Marker is already Hashable so we don’t need to do any extra work.

Since the markers that we want will be static, we will create a new static method which will return an array of Marker. This will contain all the degrees that we want to display as well as the label info that we need.

Update the above Marker to look like the following:

Step 3: Creating the CompassMarkerView

Ok, now that we have our data we can move on to the most complicated view in the app, the CompassMarkerView. It contains three child views(two Text views and one Capsule view).

The CompassMarkerView will have two properties, marker so that we can pass a Marker through from the ForEach, as well as compassDegrees, which will come from the compass logic which we will add later.

Let’s start with the code so that we can a basic visual running:

We have a VStack which contains two Text views and one Capsule view. At the moment only the Capsule as some styling.

  1. The first Text view will display the degree value of the Marker that we initialized this view with.
  2. This is going to act as a line for the current degree value to make it clearer for the user. To make this look a bit better during testing, we just add a frame for the size and foreground color.
  3. This lastText view will display the direction(i.e N, S, E, W). This direction is the Marker label value which we set when we created our Marker model in Step 2.
  4. The rotationEffect will rotate each VStack so that each marker view is at the correct angle. If you remove this you will see that all the markers will be on top of each other.

Before we build and run the app, let’s fix an issue that is causing a compile issue.

Go back to the ContentView and replace the comment // CompassViewMarker(still to come) with the following:

And replace the array with Marker.markers().

After you have updated your ContentView it should look like this:

Now we are using the correct data for our ForEach and we are creating all the CompassMarkerView views that we need.

If you build and run the app now you should see the following:

Image for post
Image for post

This is not exactly what we are going for, but it is progress :) Technically all the correct information should be there, we just have a small styling issue which we will fix now, and we should be able to see more clearly what the app will look like.

Step 4: Update styling for CompassMarkerView

Time to fix the styling. We will do this view by view, so we will do the first Text view first, then the Capsule and then the last Text view. This will not use the actual data just yet, we only want to get the styling correct at the moment.

Update the first Text view to look like this:

You can build and run now, but it will look the same as the previous step. What we have done is set the Text view up so that it can rotate later on when we get the information from the compass logic. I also just made the fontWeight light to make it look a bit better(IMO).

Next, we will update the Capsule view. Currently, we have set the frame as well as the foreground color. We will now add the padding. Update your Capsule code with the following:

As you can see, all we have done is add some bottom spacing. If you build and run now, you will see some progress.

Image for post
Image for post

We can now update our last Text view. We will be making the font bold, we will also be adding a rotationEffect and lastly padding at the bottom so that we can make the compass a bit wider.

Update the last Text view with the following code:

We set the rotationEffect to 0, this will be changed later to use the compass logic, and then we added the bottom padding of 80.

If you build and run the app now, it should look like this:

Image for post
Image for post

This is looking much better now. There are a few issues still, the first one being the degree labels having a decimal value, and the N, S, E, W labels are in the wrong position. I will talk about this at the end of the post.

This is what your CompassMarkerView code needs to look like now:

Step 5: Fix the degree labels

This is a quick fix. Add the following method to your Marker struct.

Once that is done, we need to go back to the CompassMarkerView and update the first Text view to use marker.degreeText() instead of marker.degrees.

In the CompassMarkerView we need to update the Text view code to look like the below:

If you build and run the app now, your app should look like this:

Image for post
Image for post

This is looking much better now. Let's add the final styling and then we can move on to having everything work off the compass logic.

Step 6: Final styling

In this step, we will make the Capsule for the 0 degrees marker red, we will also set different widths and heights for the Capsules, and finally, we will get the rotation for the text working with.

Add the following 4 functions to your CompassMarkerView :

  1. capsuleWidth : This method will set the width of each capsule that we have. If it is the capsule for 0 degrees, we want it to have a width of 7 and a normal capsule should have a width of 3.
  2. capsuleHeight : This is exactly the same as the capsuleWidth. For the 0 degrees capsule we want the height to be 45 and for every other capsule we want it to be 30.
  3. capsuleColor : Once again, the logic is the same as the above two, depending on the degrees we set to use a different color.
  4. textAngle : This is used to calculate the angle of the text. It uses marker degrees as well as the current compass degrees to calculate the angle that we want to apply to our Text views.

If you want, you could pass the marker and/or the compass degrees depending on the method to make these methods testable.

We now need to update our CompassMarkerView once more. Update the body property with the following:

  1. We are now using the textAngle method we just created to set the rotationEffect for our degrees Text view.
  2. We set the Capsule width using our capsuleWidth method
  3. We set the Capsule height using our capsuleHeight method
  4. We set the Capsule color using our capsuleColor method
  5. And lastly, we set the rotationEffect on our direction Text view.

This is the final CompassMarkerView code:

If you run the app now, it should look like this:

Image for post
Image for post

Finally, all the styling is done. Now we need to move onto the compass logic so that we can get this app working as expected.

Step 7: Compass logic

This is something that was driving me crazy. Trying to get this to work properly has been a struggle, and it is still not working correctly.

Create a new file called CompassHeading.swift and the following code to it:

Basically we are just setting up a locationManager and in the setup method we tell it to startUpdatingHeading.

In the locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) we multiply the heading by -1. This seems to make it work like the Apple compass app, but there are still some issues sometimes which I find odd, but it seems to exist in the Apple compass app too. I tried to use CoreMotion, but that gave me the same heading.

Step 8: Using the CompassHeading

Now that we have the CompassHeading up and running, we can use it in our ContentView.

Add the following code before the body of the ContentView:

Then the last thing that we need to do is update the rotationEffect of the ZStack in the ContentView. Update it to look like this:

If you run the app now, it should all be working, but you will need to run this on a device.

Before I end, as you can see, when you run the app, the direction labels are incorrect. I mentioned this earlier in the article. In all honesty, I could not figure this out, I tried a few things and nothing worked, so I ended up just changing the data model. As soon as I put the direction label above the Capsule in the CompassMarkerView then it worked as expected, and I am not sure why the position of the Text view would change its content, if you have any idea what could be causing this, please let me know in the comments.

This is the updated model hack:

If you run the app now, all the direction labels will be correct and your app should look like this:

Image for post
Image for post

For the final source code, you can click here.

Flawless iOS

🍏 Community around iOS development, mobile design, and…

Darren

Written by

Darren

Creator of https://imageresizer.online. Writing swift tutorials at https://programmingwithswift.com.

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Darren

Written by

Darren

Creator of https://imageresizer.online. Writing swift tutorials at https://programmingwithswift.com.

Flawless iOS

🍏 Community around iOS development, mobile design, and marketing

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store