In today’s blog post, we’ll be exploring how we can leverage Apple’s SwiftUI and Combine frameworks to improve location tracking within our iOS apps. Our use case involves a parking management app, where we want to keep track of the user’s current location to provide accurate services.
Initial State
Initially, our code had a lack of continuous location updates while the user was interacting with the app. This could potentially lead to inaccurate location data, which would affect the functionality of our parking management app.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct ParkingView: View {
@EnvironmentObject var locationService: LocationService
@State private var currentLocation: CLLocation?
var body: some View {
// Other views...
.onAppear {
os_log("Location updates started", log: viewLog, type: .debug)
locationService.startLocationUpdates()
.sink { location in
self.currentLocation = location
os_log("Current location: %@", log: viewLog, type: .debug, location.description)
}
}
}
}
Continuous Location Updates
To make location tracking more accurate, we decided to implement continuous location updates.
First, we made changes in LocationService
by replacing the getCurrentLocation()
method with startLocationUpdates()
. This method would return a publisher emitting new locations as they were updated by the CLLocationManager
.
1
2
3
4
5
6
7
8
9
class LocationService: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
private var locationSubject = PassthroughSubject<CLLocation, Never>()
func startLocationUpdates() -> AnyPublisher<CLLocation, Never> {
locationManager.startUpdatingLocation()
return locationSubject.eraseToAnyPublisher()
}
}
Next, we modified the SwiftUI View
’s .onAppear
method to subscribe to this publisher, storing the returned AnyCancellable
in a @State
property. This is important because we need to manage the lifecycle of the subscription and make sure it gets cancelled when the view disappears.
In the .onAppear
method, we started the location updates and stored the new location updates in currentLocation
:
1
2
3
4
5
6
.onAppear {
self.locationUpdateCancellable = self.locationService.startLocationUpdates()
.sink { location in
self.currentLocation = location
}
}
To make sure that location updates stop when the view is no longer on screen, we added an .onDisappear
method that cancels the subscription and stops location updates:
1
2
3
4
.onDisappear {
self.locationService.stopUpdatingLocation()
self.locationUpdateCancellable?.cancel()
}
Enhanced Button Actions
We also improved the action buttons within the app. Each button has a role-based action which uses the camera to scan VIN numbers and license plates, and then sends the updated status of the vehicle to the backend.
Here is an anonymized example of an action button within the ParkingView
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private func actionButton(title: String, iconName: String, statusText: String, action: @escaping () -> Void) -> some View {
Button(action: {
AVCaptureDevice.requestAccess(for: .video) { granted in
if granted {
action()
self.vehicleStatus = statusText
self.isShowingScannerModal = true
} else {
os_log("Camera access not granted", log: errorLog, type: .error)
}
}
}) {
HStack {
Image(systemName: "qrcode")
Text(title)
Image(systemName: iconName)
}
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
Conclusion
With these changes, our location tracking is now more accurate and efficient. The use of Combine allowed us to reactively update our app’s state based on the user’s location, and SwiftUI made it easy to manage the lifecycle of these updates. Additionally, the role-based actions provide a user-friendly way to update vehicle statuses within the parking management app.
[YouTube Video Placeholder]
In our next post, we’ll explore further improvements we can make to our parking management app using more advanced SwiftUI and Combine techniques. Stay tuned!
1
tags: [SwiftUI, Combine, iOS, Location Services]