Skip to content

Commit

Permalink
Merge pull request #2 from ezefranca/feature/update_delegate
Browse files Browse the repository at this point in the history
Created didUpdateAuthorizationStatus and speedManagerDidFailWithLocat…
  • Loading branch information
ezefranca authored Jul 8, 2024
2 parents d4797b1 + 38b4eb5 commit b5a1a80
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 85 deletions.
4 changes: 2 additions & 2 deletions Demo/Demo/Demo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
34 changes: 14 additions & 20 deletions Demo/Demo/Demo/CustomGauge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -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
Expand Down
30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -61,7 +61,6 @@ Or add the info to the Info.plist

## Usage example


### @StateObject

```swift
Expand All @@ -77,7 +76,6 @@ struct ContentView: View {
case .authorized:
Text("Your current speed is:")
Text("\(speedManager.speed)")
Text("km/h")
default:
Spacer()
}
Expand All @@ -88,8 +86,7 @@ struct ContentView: View {

### Using Delegates

``` swift

```swift
import UIKit

class SpeedViewController: UIViewController {
Expand All @@ -105,40 +102,45 @@ 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

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)

Expand Down
117 changes: 76 additions & 41 deletions Sources/SpeedManagerModule/SpeedManager.swift
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// Enumeration representing the authorization status for the speed manager.
public enum SpeedManagerAuthorizationStatus {
case authorized
case notDetermined
case authorized
case denied
}
Loading

0 comments on commit b5a1a80

Please sign in to comment.