diff --git a/Demo/Demo/Demo/ContentView.swift b/Demo/Demo/Demo/ContentView.swift index 326c22a..a9fe9c5 100644 --- a/Demo/Demo/Demo/ContentView.swift +++ b/Demo/Demo/Demo/ContentView.swift @@ -9,14 +9,14 @@ import SwiftUI import SpeedManagerModule struct ContentView: View { - @StateObject var speedManager = SpeedManager(.kilometersPerHour) + @StateObject var speedManager = SpeedManager(speedUnit: .kilometersPerHour) var body: some View { VStack { switch speedManager.authorizationStatus { case .authorized: Text("Your current speed is:") - Text("\(speedManager.speed.fixed())") + Text("\(speedManager.speed.fixedToOneDecimal())") Text("km/h") CustomGauge(currentSpeed: $speedManager.speed) diff --git a/Demo/Demo/Demo/CustomGauge.swift b/Demo/Demo/Demo/CustomGauge.swift index 9608a66..570306a 100644 --- a/Demo/Demo/Demo/CustomGauge.swift +++ b/Demo/Demo/Demo/CustomGauge.swift @@ -5,78 +5,72 @@ // Created by Ezequiel Santos on 19/12/2022. // -import SwiftUI - import SwiftUI import Foundation struct CustomGauge: View { - @Binding var currentSpeed: Double - + var body: some View { if #available(macOS 13.0, *) { Gauge(value: currentSpeed, in: 0...200) { Image(systemName: "gauge.medium") .font(.system(size: 50.0)) } currentValueLabel: { - Text("\(currentSpeed.fixed().formatted(.number))") - + Text("\(currentSpeed.fixedToOneDecimal().formatted(.number))") + .monospaced() } .gaugeStyle(SpeedometerGaugeStyle()) + .animation(nil, value: currentSpeed) // Disable animations for gauge updates } else { // Fallback on earlier versions } - } } struct SpeedometerGaugeStyle: GaugeStyle { - private var purpleGradient = LinearGradient(gradient: Gradient(colors: [ Color(red: 207/255, green: 150/255, blue: 207/255), Color(red: 107/255, green: 116/255, blue: 179/255) ]), startPoint: .trailing, endPoint: .leading) - + private var purpleGradient = LinearGradient(gradient: Gradient(colors: [Color(red: 207/255, green: 150/255, blue: 207/255), Color(red: 107/255, green: 116/255, blue: 179/255)]), startPoint: .trailing, endPoint: .leading) + func makeBody(configuration: Configuration) -> some View { ZStack { - Circle() .foregroundColor(Color(.lightGray)) - + Circle() .trim(from: 0, to: 0.75 * configuration.value) .stroke(purpleGradient, lineWidth: 20) .rotationEffect(.degrees(135)) - + Circle() .trim(from: 0, to: 0.75) .stroke(Color.black, style: StrokeStyle(lineWidth: 10, lineCap: .butt, lineJoin: .round, dash: [1, 34], dashPhase: 0.0)) .rotationEffect(.degrees(135)) - + VStack { configuration.currentValueLabel .font(.system(size: 80, weight: .bold, design: .rounded)) .foregroundColor(.gray) + .monospaced() Text("KM/H") .font(.system(.body, design: .rounded)) .bold() .foregroundColor(.gray) } - } .frame(width: 300, height: 300) - } - } struct Gauge_Previews: PreviewProvider { static var previews: some View { - CustomGauge(currentSpeed: .constant(100)) + CustomGauge(currentSpeed: .constant(100.0)) } } extension Double { - - func fixed() -> Double { - Double(String(format:"%.2f", self)) ?? self + /// Returns the double value fixed to one decimal place. + func fixedToOneDecimal() -> Double { + (self * 10).rounded() / 10 } } diff --git a/Package.swift b/Package.swift index 8c31e97..5ed7c00 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.5 +// swift-tools-version: 5.10 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/README.md b/README.md index 5d0df6e..8460e40 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ I like to measure my speed inside trains and buses. When I was searching for a s ## Installation -The Swift Package Manager is the easiest way to install and manage SpeedManagerModule as a dependecy. +The Swift Package Manager is the easiest way to install and manage SpeedManagerModule as a dependency. Simply add SpeedManagerModule to your dependencies in your Package.swift file: ```swift @@ -61,7 +61,6 @@ Or add the info to the Info.plist ## Usage example - ### @StateObject ```swift @@ -77,7 +76,6 @@ struct ContentView: View { case .authorized: Text("Your current speed is:") Text("\(speedManager.speed)") - Text("km/h") default: Spacer() } @@ -88,8 +86,7 @@ struct ContentView: View { ### Using Delegates -``` swift - +```swift import UIKit class SpeedViewController: UIViewController { @@ -105,13 +102,22 @@ class SpeedViewController: UIViewController { extension SpeedViewController: SpeedManagerDelegate { - func speedManager(_ manager: SpeedManager, didUpdateSpeed speed: Double) { + func speedManager(_ manager: SpeedManager, didUpdateSpeed speed: Double, speedAccuracy: Double) { + // Update UI with the current speed and accuracy } func speedManager(_ manager: SpeedManager, didFailWithError error: Error) { + // Handle error + } + + func speedManager(_ speedManager: SpeedManager, didUpdateAuthorizationStatus status: SpeedManagerAuthorizationStatus) { + // Handle authorization status update + } + + func speedManagerDidFailWithLocationServicesUnavailable(_ speedManager: SpeedManager) { + // Handle location services unavailable } } - ``` ### Changing Unit @@ -119,26 +125,22 @@ extension SpeedViewController: SpeedManagerDelegate { Just choose the unit during the class init. ```swift - var speedManagerKmh = SpeedManager(.kilometersPerHour) - var speedManagerMs = SpeedManager(.meterPerSecond) + var speedManagerMs = SpeedManager(.metersPerSecond) var speedManagerMph = SpeedManager(.milesPerHour) - ``` ### Demo -Check the ```Demo``` folder to see it in action. - +Check the `Demo` folder to see it in action. https://user-images.githubusercontent.com/3648336/208701407-ebf7319f-32c1-45bc-adc7-aa8509f0336d.mov - ## Meta @ezefranca – [@ezefranca](https://twitter.com/ezefranca) -Distributed under the MIT license. See ``LICENSE`` for more information. +Distributed under the MIT license. See `LICENSE` for more information. [https://github.com/ezefranca/SpeedManagerModule](https://github.com/ezefranca/SpeedManagerModule) diff --git a/Sources/SpeedManagerModule/SpeedManager.swift b/Sources/SpeedManagerModule/SpeedManager.swift index 305d2b6..fd31924 100644 --- a/Sources/SpeedManagerModule/SpeedManager.swift +++ b/Sources/SpeedManagerModule/SpeedManager.swift @@ -1,34 +1,59 @@ import Foundation import CoreLocation -public protocol SpeedManagerTrigger { - func startUpdatingSpeed() - func startMonitoringSpeed() -} - -public class SpeedManager : NSObject, ObservableObject, SpeedManagerTrigger { +/// A class that manages the monitoring and updating of speed using CoreLocation. +public class SpeedManager: NSObject, ObservableObject, SpeedManagerTrigger { + + // MARK: - Properties - // MARK: Private + /// The CoreLocation manager used to get location updates. private let locationManager = CLLocationManager() + + /// The unit of speed to be used. private var speedUnit: SpeedManagerUnit + + /// The trigger for starting speed updates. private var trigger: SpeedManagerTrigger? - private var allowsBackgroundLocationUpdates: Bool = false - - // MARK: Public - public var delegate: SpeedManagerDelegate? - - @Published public var authorizationStatus: SpeedManagerAuthorizationStatus = .notDetermined - @Published public var speed: Double = 0 - @Published public var speedAccuracy: Double = 0 + + /// Indicates whether background location updates are allowed. + private var allowsBackgroundLocationUpdates: Bool + + /// The delegate to receive updates from the SpeedManager. + public weak var delegate: SpeedManagerDelegate? + + /// The current authorization status for location services. + @Published public private(set) var authorizationStatus: SpeedManagerAuthorizationStatus = .notDetermined { + didSet { + DispatchQueue.main.async { + self.delegate?.speedManager(self, didUpdateAuthorizationStatus: self.authorizationStatus) + } + } + } + + /// The current speed. + @Published public var speed: Double = 0 { + didSet { + DispatchQueue.main.async { + self.delegate?.speedManager(self, didUpdateSpeed: self.speed, speedAccuracy: self.speedAccuracy) + } + } + } + + /// The accuracy of the current speed. + @Published public private(set) var speedAccuracy: Double = 0 - private var isRequestingLocation = false + // MARK: - Initializer - public init(_ speedUnit: SpeedManagerUnit, + /// Initializes a new SpeedManager. + /// - Parameters: + /// - speedUnit: The unit of speed measurement. + /// - trigger: An optional trigger for starting speed updates. If nil, the SpeedManager will trigger itself. + /// - allowsBackgroundLocationUpdates: A Boolean value indicating whether background location updates are allowed. + public init(speedUnit: SpeedManagerUnit, trigger: SpeedManagerTrigger? = nil, allowsBackgroundLocationUpdates: Bool = false) { self.speedUnit = speedUnit - self.delegate = nil self.allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates super.init() self.trigger = trigger ?? self @@ -40,63 +65,73 @@ public class SpeedManager : NSObject, ObservableObject, SpeedManagerTrigger { self.locationManager.requestAlwaysAuthorization() } + // MARK: - Public Methods + + /// Starts updating the speed. public func startUpdatingSpeed() { trigger?.startMonitoringSpeed() } + /// Starts monitoring the speed. public func startMonitoringSpeed() { - - switch self.authorizationStatus { - + switch authorizationStatus { case .authorized: - if allowsBackgroundLocationUpdates { self.locationManager.allowsBackgroundLocationUpdates = true + if allowsBackgroundLocationUpdates { + locationManager.allowsBackgroundLocationUpdates = true } - self.locationManager.startUpdatingLocation() + locationManager.startUpdatingLocation() case .notDetermined: - self.locationManager.requestAlwaysAuthorization() + locationManager.requestAlwaysAuthorization() case .denied: - fatalError("No location services available") + DispatchQueue.main.async { + self.delegate?.speedManagerDidFailWithLocationServicesUnavailable(self) + } } } } -// MARK: CLLocationManagerDelegate methods +// MARK: - CLLocationManagerDelegate extension SpeedManager: CLLocationManagerDelegate { + /// Called when the authorization status changes. + /// - Parameter manager: The location manager reporting the change. public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { switch manager.authorizationStatus { - - case .authorizedWhenInUse, - .authorizedAlways: + case .authorizedWhenInUse, .authorizedAlways: authorizationStatus = .authorized - locationManager.requestLocation() - break - + locationManager.requestLocation() case .notDetermined: authorizationStatus = .notDetermined manager.requestWhenInUseAuthorization() - break - default: authorizationStatus = .denied } - self.startMonitoringSpeed() + startMonitoringSpeed() } + /// Called when new location data is available. + /// - Parameters: + /// - manager: The location manager providing the data. + /// - locations: An array of new location data objects. public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + guard let lastLocation = locations.last else { return } - let currentSpeed = locations.last?.speed ?? 0 + let currentSpeed = lastLocation.speed speed = currentSpeed >= 0 ? currentSpeed * speedUnit.rawValue : .nan - speedAccuracy = locations.last?.speedAccuracy ?? .nan + speedAccuracy = lastLocation.speedAccuracy - self.delegate?.speedManager(self, didUpdateSpeed: speed, speedAccuracy: speedAccuracy) - - self.locationManager.requestLocation() + locationManager.requestLocation() } + /// Called when the location manager encounters an error. + /// - Parameters: + /// - manager: The location manager reporting the error. + /// - error: The error encountered by the location manager. public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { - self.delegate?.speedManager(self, didFailWithError: error) + DispatchQueue.main.async { + self.delegate?.speedManager(self, didFailWithError: error) + } } } diff --git a/Sources/SpeedManagerModule/SpeedManagerAuthorizationStatus.swift b/Sources/SpeedManagerModule/SpeedManagerAuthorizationStatus.swift index 5a0b895..cddb376 100644 --- a/Sources/SpeedManagerModule/SpeedManagerAuthorizationStatus.swift +++ b/Sources/SpeedManagerModule/SpeedManagerAuthorizationStatus.swift @@ -1,5 +1,6 @@ +/// Enumeration representing the authorization status for the speed manager. public enum SpeedManagerAuthorizationStatus { - case authorized case notDetermined + case authorized case denied } diff --git a/Sources/SpeedManagerModule/SpeedManagerDelegate.swift b/Sources/SpeedManagerModule/SpeedManagerDelegate.swift index 6f92db0..e190506 100644 --- a/Sources/SpeedManagerModule/SpeedManagerDelegate.swift +++ b/Sources/SpeedManagerModule/SpeedManagerDelegate.swift @@ -1,6 +1,16 @@ import Foundation -public protocol SpeedManagerDelegate { - func speedManager(_ manager: SpeedManager, didUpdateSpeed speed: Double, speedAccuracy: Double) - func speedManager(_ manager: SpeedManager, didFailWithError error: Error) +/// Protocol defining the delegate methods for the SpeedManager. +public protocol SpeedManagerDelegate: AnyObject { + /// Called when the speed manager updates the speed. + func speedManager(_ speedManager: SpeedManager, didUpdateSpeed speed: Double, speedAccuracy: Double) + + /// Called when the speed manager encounters an error. + func speedManager(_ speedManager: SpeedManager, didFailWithError error: Error) + + /// Called when the authorization status changes. + func speedManager(_ speedManager: SpeedManager, didUpdateAuthorizationStatus status: SpeedManagerAuthorizationStatus) + + /// Called when location services are not available. + func speedManagerDidFailWithLocationServicesUnavailable(_ speedManager: SpeedManager) } diff --git a/Sources/SpeedManagerModule/SpeedManagerTrigger.swift b/Sources/SpeedManagerModule/SpeedManagerTrigger.swift new file mode 100644 index 0000000..23f62eb --- /dev/null +++ b/Sources/SpeedManagerModule/SpeedManagerTrigger.swift @@ -0,0 +1,8 @@ +/// Protocol defining the necessary methods for triggering speed updates. +public protocol SpeedManagerTrigger { + /// Starts the process for updating speed. + func startUpdatingSpeed() + + /// Starts monitoring the speed. + func startMonitoringSpeed() +} diff --git a/Sources/SpeedManagerModule/SpeedManagerUnit.swift b/Sources/SpeedManagerModule/SpeedManagerUnit.swift index 914d587..9889c1a 100644 --- a/Sources/SpeedManagerModule/SpeedManagerUnit.swift +++ b/Sources/SpeedManagerModule/SpeedManagerUnit.swift @@ -1,8 +1,8 @@ import Foundation +/// Enumeration representing the units of speed measurement. public enum SpeedManagerUnit: Double { - - case meterPerSecond = 1.0 + case metersPerSecond = 1.0 case kilometersPerHour = 3.6 - case milesPerHour = 2.2369 + case milesPerHour = 2.23694 }