iOS

Build a Compass app with SwiftUI

Build a Compass app with SwiftUI

In this iOS tutorial, we will be exploring how to build a compass app using SwiftUI in Swift programming language. Although the SwiftUI code for this app is only around 100 lines, it was quite challenging to develop and took much longer than expected. Join us on this coding journey as we learn, share, and explore the process of building a simple compass app together on Codeplayon.

This post focuses on the SwiftUI aspect of the compass rather than the compass logic itself (CoreLocation). The tutorial will mainly cover how to ensure proper rotations and padding, such as making sure the text remains readable even when the compass is turning, and spacing out the degree views evenly.

Build an iOS Compass app using SwiftUI

let’s create a new project open your XCode and create a new project. if you have an existing iOS project can create a new swift class for the Location manager and the UI for creating a ContentView.

Step 1: Create a LocationManger Swift Class CompassHeading

import Foundation
import Combine
import CoreLocation

class CompassHeading: NSObject, ObservableObject, CLLocationManagerDelegate {

    var objectWillChange = PassthroughSubject<Void, Never>()
    var degrees: Double = .zero {
        didSet {
            objectWillChange.send()
        }
    }


    private let locationManager: CLLocationManager

    override init() {
        self.locationManager = CLLocationManager()
        super.init()
        self.locationManager.delegate = self
        self.setup()
    }

    
    private func setup() {
        self.locationManager.requestWhenInUseAuthorization()

       
        if CLLocationManager.headingAvailable() {
            self.locationManager.startUpdatingLocation()
            self.locationManager.startUpdatingHeading()
        }
    }

    
    func locationManager(_ manager: CLLocationManager, 
didUpdateHeading newHeading: CLHeading) {
        self.degrees = -1 * newHeading.magneticHeading
    }
}

 

Step 2:  Create a CompassView Swift class 

import Foundation
import SwiftUI

struct LocMarker: Hashable {

    let degrees: Double
    let label: String

    init(degrees: Double, label: String = "") {
        self.degrees = degrees
        self.label = label
    }

    func degreeText() -> String {
        return String(format: "%.0f", self.degrees)
    }

    static func markers() -> [LocMarker] {
        return [
            LocMarker(degrees: 0, label: "N"),
            LocMarker(degrees: 30),
            LocMarker(degrees: 60),
            LocMarker(degrees: 90, label: "E"),
            LocMarker(degrees: 120),
            LocMarker(degrees: 150),
            LocMarker(degrees: 180, label: "S"),
            LocMarker(degrees: 210),
            LocMarker(degrees: 240),
            LocMarker(degrees: 270, label: "W"),
            LocMarker(degrees: 300),
            LocMarker(degrees: 330)
        ]
    }
}

struct CompassMarkerView: View {
    let marker: LocMarker
    let compassDegress: Double
    var body: some View {
        VStack {
            Text(marker.degreeText())
                .fontWeight(.light)
                .rotationEffect(self.textAngle())

            Capsule()
                .frame(width: self.capsuleWidth(),
                       height: self.capsuleHeight())
                .foregroundColor(self.capsuleColor())
           

            Text(marker.label)
                .fontWeight(.bold)
                .rotationEffect(self.textAngle())
                .padding(.bottom, 180)
        }.rotationEffect(Angle(degrees: marker.degrees))
    }

    

    private func capsuleWidth() -> CGFloat {
        return self.marker.degrees == 0 ? 7 : 3
    }

    private func capsuleHeight() -> CGFloat {
        return self.marker.degrees == 0 ? 45 : 30
    }

    private func capsuleColor() -> Color {
        return self.marker.degrees == 0 ? .red : .gray
    }

    private func textAngle() -> Angle {
        return Angle(degrees: -self.compassDegress - self.marker.degrees)
    }
}

struct ContentView : View {

    @ObservedObject var compassHeading = CompassHeading()

    var body: some View {
                VStack {
                Capsule()
                    .frame(width: 5,
                           height: 50)
                ZStack {
                    ForEach(LocMarker.markers(), id: \.self) { marker in
                        CompassMarkerView(marker: marker,
                                          compassDegress: self.compassHeading.degrees)
                    }
                }
                .frame(width: 300,
                       height: 300)
                .rotationEffect(Angle(degrees: self.compassHeading.degrees))
                .statusBar(hidden: true)
        }
    }
}

In the above code, for creating a compass App UI. We added a couple of things. We added a ZStack with some styling as well as a ForEach that will display the Compass ViewMarkers.

  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.

 

Read More Tutorial