Files
Swift/Speedo/LocationManager.swift
T

157 lines
5.6 KiB
Swift
Raw Normal View History

2025-05-05 09:33:00 -07:00
//
// LocationManager.swift
// Speedometer
//
// Created by Justin Oros on 5/2/2025.
//
import Foundation
import CoreLocation
2025-05-05 17:49:07 -07:00
import SwiftUI
2025-05-05 09:33:00 -07:00
2025-05-05 17:49:07 -07:00
/// A class responsible for managing and publishing location and speed updates.
2025-05-05 09:33:00 -07:00
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
2025-05-05 17:49:07 -07:00
/// The underlying Core Location manager instance.
2025-05-05 09:33:00 -07:00
private let manager = CLLocationManager()
2025-05-05 17:49:07 -07:00
// MARK: - Published Properties (UI-bound)
/// Current speed in MPH (calculated from CLLocation speed).
@Published var speed: Int = 0
/// Most recent known location.
2025-05-05 09:33:00 -07:00
@Published var location: CLLocation?
2025-05-05 17:49:07 -07:00
/// Horizontal accuracy in meters.
2025-05-05 09:33:00 -07:00
@Published var accuracy: Double = 0.0
2025-05-05 17:49:07 -07:00
/// Current authorization status.
2025-05-05 09:33:00 -07:00
@Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
2025-05-05 17:49:07 -07:00
/// Flag to show alert when location permissions are problematic.
2025-05-05 09:33:00 -07:00
@Published var showAlert = false
2025-05-05 17:49:07 -07:00
/// Message to display in the alert.
2025-05-05 09:33:00 -07:00
@Published var alertMessage = ""
2025-05-05 17:49:07 -07:00
// MARK: - Initialization
2025-05-05 09:33:00 -07:00
override init() {
super.init()
manager.delegate = self
manager.activityType = .automotiveNavigation
manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
2025-05-05 17:49:07 -07:00
manager.distanceFilter = 1 // Update for every meter moved
2025-05-05 09:33:00 -07:00
print("LocationManager initialized")
2025-05-05 17:49:07 -07:00
requestAuthorization()
startUpdatingLocationIfNeeded()
2025-05-05 09:33:00 -07:00
}
2025-05-05 17:49:07 -07:00
// MARK: - Authorization
/// Requests location permission from the user.
2025-05-05 09:33:00 -07:00
private func requestAuthorization() {
print("Requesting location authorization")
2025-05-05 17:49:07 -07:00
manager.requestWhenInUseAuthorization()
2025-05-05 09:33:00 -07:00
}
2025-05-05 17:49:07 -07:00
/// Starts updating location only if authorization is granted.
2025-05-05 09:33:00 -07:00
private func startUpdatingLocationIfNeeded() {
if authorizationStatus == .authorizedAlways || authorizationStatus == .authorizedWhenInUse {
print("Attempting to start location updates")
2025-05-05 17:49:07 -07:00
manager.startUpdatingLocation()
2025-05-05 09:33:00 -07:00
} else if authorizationStatus == .denied || authorizationStatus == .restricted {
print("Location updates not started due to denied/restricted authorization")
DispatchQueue.main.async {
self.showAlert = true
self.alertMessage = "Location services are disabled. Please enable them in Settings."
}
} else {
2025-05-05 17:49:07 -07:00
print("Authorization not yet determined: \(authorizationStatus.rawValue)")
2025-05-05 09:33:00 -07:00
}
}
2025-05-05 17:49:07 -07:00
/// Called whenever the app's location authorization status changes.
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
let status = manager.authorizationStatus
2025-05-05 09:33:00 -07:00
DispatchQueue.main.async {
self.authorizationStatus = status
2025-05-05 17:49:07 -07:00
print("Authorization status changed: \(status.rawValue)")
self.startUpdatingLocationIfNeeded()
2025-05-05 09:33:00 -07:00
}
2025-05-05 17:49:07 -07:00
// Handle alert messaging for denied or restricted access
2025-05-05 09:33:00 -07:00
switch status {
case .authorizedAlways:
print("Location authorization: Authorized Always")
case .authorizedWhenInUse:
print("Location authorization: Authorized When In Use")
case .denied:
print("Location authorization: Denied")
DispatchQueue.main.async {
self.showAlert = true
self.alertMessage = "Location access was denied. Please enable it in Settings for the app to function correctly."
}
case .restricted:
print("Location authorization: Restricted")
DispatchQueue.main.async {
self.showAlert = true
self.alertMessage = "Location access is restricted. This might be due to parental controls or system settings."
}
case .notDetermined:
print("Location authorization: Not Determined")
@unknown default:
print("Location authorization: Unknown status")
}
}
2025-05-05 17:49:07 -07:00
// MARK: - Location Updates
/// Called whenever new location data is available.
2025-05-05 09:33:00 -07:00
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let latest = locations.last else {
2025-05-05 17:49:07 -07:00
print("No latest location found")
2025-05-05 09:33:00 -07:00
return
}
2025-05-05 18:25:05 -07:00
// Skip updates if accuracy is too poor (≥ 20 meters)
guard latest.horizontalAccuracy >= 0 && latest.horizontalAccuracy < 20 else {
print("Skipping update due to poor accuracy: \(latest.horizontalAccuracy)")
return
}
2025-05-05 17:49:07 -07:00
// Logging raw location data for debugging
print("New location: \(latest.coordinate.latitude), \(latest.coordinate.longitude)")
2025-05-05 09:33:00 -07:00
print("Speed (m/s): \(latest.speed)")
print("Accuracy: \(latest.horizontalAccuracy)")
DispatchQueue.main.async {
2025-05-05 18:25:05 -07:00
// Ensure speed is non-negative
2025-05-05 17:49:07 -07:00
let rawSpeed = max(latest.speed, 0)
2025-05-05 18:25:05 -07:00
// Ignore very small fluctuations when stopped (less than 0.5 m/s)
let filteredSpeed = rawSpeed < 0.5 ? 0 : rawSpeed
2025-05-05 17:49:07 -07:00
2025-05-05 18:25:05 -07:00
// Convert from m/s to MPH
let mph = filteredSpeed * 2.23694
// Update UI-bound values
2025-05-05 09:33:00 -07:00
self.speed = Int(mph.rounded())
self.location = latest
self.accuracy = latest.horizontalAccuracy
}
}
2025-05-05 17:49:07 -07:00
/// Called when location updates fail.
2025-05-05 09:33:00 -07:00
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location update failed: \(error.localizedDescription)")
DispatchQueue.main.async {
self.showAlert = true
self.alertMessage = "Location update failed: \(error.localizedDescription)"
}
}
}