├── .gitattributes
├── banner.jpg
├── .github
├── backgroundmodes.png
└── workflows
│ └── swift.yml
├── Demo
└── Demo
│ ├── Demo
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── DemoApp.swift
│ ├── Info.plist
│ ├── Demo.entitlements
│ └── ContentView.swift
│ └── Demo.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
│ └── project.pbxproj
├── .gitignore
├── Sources
└── SpeedManagerModule
│ ├── SpeedManagerAuthorizationStatus.swift
│ ├── SpeedManagerUnit.swift
│ ├── SpeedManagerTrigger.swift
│ ├── SpeedManagerDelegate.swift
│ └── SpeedManager.swift
├── Package.swift
├── LICENSE
├── Tests
└── SpeedManagerModuleTests
│ └── SpeedManagerModuleTests.swift
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ezefranca/SpeedManagerModule/HEAD/banner.jpg
--------------------------------------------------------------------------------
/.github/backgroundmodes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ezefranca/SpeedManagerModule/HEAD/.github/backgroundmodes.png
--------------------------------------------------------------------------------
/Demo/Demo/Demo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/SpeedManagerModule/SpeedManagerAuthorizationStatus.swift:
--------------------------------------------------------------------------------
1 | /// Enumeration representing the authorization status for the speed manager.
2 | public enum SpeedManagerAuthorizationStatus {
3 | case notDetermined
4 | case authorized
5 | case denied
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/SpeedManagerModule/SpeedManagerUnit.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Enumeration representing the units of speed measurement.
4 | public enum SpeedManagerUnit: Double {
5 | case metersPerSecond = 1.0
6 | case kilometersPerHour = 3.6
7 | case milesPerHour = 2.23694
8 | }
9 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo/DemoApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DemoApp.swift
3 | // Demo
4 | //
5 | // Created by Ezequiel Santos on 19/12/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct DemoApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIBackgroundModes
6 |
7 | location
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sources/SpeedManagerModule/SpeedManagerTrigger.swift:
--------------------------------------------------------------------------------
1 | /// Protocol defining the necessary methods for triggering speed updates.
2 | public protocol SpeedManagerTrigger {
3 | /// Starts the process for updating speed.
4 | func startUpdatingSpeed()
5 |
6 | /// Starts monitoring the speed.
7 | func startMonitoringSpeed()
8 | }
9 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo/Demo.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.personal-information.location
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "f85d5e4b04b250738b975a05ca154d748cfd11cfef3306ae68f6356d4cb9d1c2",
3 | "pins" : [
4 | {
5 | "identity" : "speedometerswiftui",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/LidorFadida/SpeedometerSwiftUI.git",
8 | "state" : {
9 | "branch" : "main",
10 | "revision" : "c51afc0b1014489dc20cb5a7a5bc7141611e61e8"
11 | }
12 | }
13 | ],
14 | "version" : 3
15 | }
16 |
--------------------------------------------------------------------------------
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Swift project
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
3 |
4 | name: Swift
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | pull_request:
10 | branches: [ "main" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: macos-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Build
20 | run: swift build -v
21 | - name: Run tests
22 | run: swift test -v
23 |
--------------------------------------------------------------------------------
/Sources/SpeedManagerModule/SpeedManagerDelegate.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Protocol defining the delegate methods for the SpeedManager.
4 | public protocol SpeedManagerDelegate: AnyObject {
5 | /// Called when the speed manager updates the speed.
6 | func speedManager(_ speedManager: SpeedManager, didUpdateSpeed speed: Double, speedAccuracy: Double)
7 |
8 | /// Called when the speed manager encounters an error.
9 | func speedManager(_ speedManager: SpeedManager, didFailWithError error: Error)
10 |
11 | /// Called when the authorization status changes.
12 | func speedManager(_ speedManager: SpeedManager, didUpdateAuthorizationStatus status: SpeedManagerAuthorizationStatus)
13 |
14 | /// Called when location services are not available.
15 | func speedManagerDidFailWithLocationServicesUnavailable(_ speedManager: SpeedManager)
16 | }
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SpeedManagerModule",
8 | platforms: [
9 | .macOS(.v12),
10 | .iOS(.v15),
11 | .watchOS(.v8)
12 | ],
13 | products: [
14 | .library(
15 | name: "SpeedManagerModule",
16 | targets: ["SpeedManagerModule"]),
17 | ],
18 | dependencies: [
19 | // Dependencies declare other packages that this package depends on.
20 | // .package(url: /* package url */, from: "1.0.0"),
21 | ],
22 | targets: [
23 | .target(
24 | name: "SpeedManagerModule",
25 | dependencies: []),
26 | .testTarget(
27 | name: "SpeedManagerModuleTests",
28 | dependencies: ["SpeedManagerModule"]),
29 | ],
30 | swiftLanguageVersions: [SwiftVersion.v5]
31 | )
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ezequiel Santos
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "1x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "2x",
16 | "size" : "16x16"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "1x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "2x",
26 | "size" : "32x32"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "2x",
36 | "size" : "128x128"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "1x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "2x",
46 | "size" : "256x256"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "1x",
51 | "size" : "512x512"
52 | },
53 | {
54 | "idiom" : "mac",
55 | "scale" : "2x",
56 | "size" : "512x512"
57 | }
58 | ],
59 | "info" : {
60 | "author" : "xcode",
61 | "version" : 1
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo/ContentView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import SpeedManagerModule
3 | import SpeedometerSwiftUI
4 |
5 | extension Double {
6 | /// Returns the double value fixed to one decimal place.
7 | func fixedToOneDecimal() -> String {
8 | return String(format: "%.1f", self)
9 | }
10 | }
11 |
12 | struct ContentView: View {
13 | @StateObject var speedManager = SpeedManager(speedUnit: .kilometersPerHour)
14 | @State var progress: CGFloat = 0.0
15 | @State private var speed: TimeInterval = 0.01
16 | let maxSpeed: CGFloat = 200.0 // Define a constant maximum speed
17 |
18 | var body: some View {
19 | VStack {
20 | switch speedManager.authorizationStatus {
21 | case .authorized:
22 | Text("Your current speed is:")
23 | .monospaced()
24 | Text("\(speedManager.speed.fixedToOneDecimal()) km/h")
25 | .monospaced()
26 |
27 | TimelineView(.animation(minimumInterval: speed)) { context in
28 | GaugeView(
29 | animationDuration: speed,
30 | progress: progress,
31 | numberOfSegments: 200,
32 | step: 20
33 | )
34 | .onChange(of: context.date) { oldValue, newValue in
35 | updateProgress()
36 | }
37 | .frame(width: 300, height: 300)
38 | }
39 |
40 | default:
41 | Text("Check your location permissions...")
42 | ProgressView()
43 | }
44 | }
45 | }
46 |
47 | private func updateProgress() {
48 | withAnimation(.easeInOut(duration: 0.5)) {
49 | progress = CGFloat(speedManager.speed / maxSpeed)
50 | }
51 | speed = 0.01 // Adjust speed interval as needed
52 | }
53 | }
54 |
55 | struct ContentView_Previews: PreviewProvider {
56 | static var previews: some View {
57 | ContentView()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Tests/SpeedManagerModuleTests/SpeedManagerModuleTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import SpeedManagerModule
3 |
4 | final class SpeedManagerModuleTests: XCTestCase {
5 |
6 | var manager: SpeedManager?
7 |
8 | func test_speed() throws {
9 | let mockDelegate = SpeedManagerDelegateMock(testCase: self)
10 | manager = SpeedManager(speedUnit: .kilometersPerHour, trigger: self)
11 | manager?.delegate = mockDelegate
12 |
13 | mockDelegate.expectSpeed()
14 | manager?.startUpdatingSpeed()
15 |
16 | waitForExpectations(timeout: 1)
17 |
18 | let result = try XCTUnwrap(mockDelegate.speed)
19 | XCTAssertEqual(result, 12.2)
20 | }
21 |
22 | func test_speedAccuracy() throws {
23 | let mockDelegate = SpeedManagerDelegateMock(testCase: self)
24 | manager = SpeedManager(speedUnit: .kilometersPerHour, trigger: self)
25 | manager?.delegate = mockDelegate
26 |
27 | mockDelegate.expectSpeed()
28 | manager?.startUpdatingSpeed()
29 |
30 | waitForExpectations(timeout: 1)
31 |
32 | XCTAssertEqual(mockDelegate.speedAccuracy, 1)
33 | }
34 | }
35 |
36 | extension SpeedManagerModuleTests: SpeedManagerTrigger {
37 | func startMonitoringSpeed() {
38 | guard let manager = manager else { return }
39 | self.manager?.delegate?.speedManager(manager,
40 | didUpdateSpeed: 12.2,
41 | speedAccuracy: 1)
42 | }
43 |
44 | func startUpdatingSpeed() {
45 | self.startMonitoringSpeed()
46 | }
47 | }
48 |
49 | class SpeedManagerDelegateMock: SpeedManagerDelegate {
50 |
51 | var speed: Double?
52 | var speedAccuracy: Double?
53 |
54 | private var expectation: XCTestExpectation?
55 | private let testCase: XCTestCase
56 |
57 | var didUpdateSpeed: Bool = false
58 | var didFailWithError: Bool = false
59 | var didUpdateAuthorizationStatus: Bool = false
60 | var speedManagerDidFailWithLocationServicesUnavailable: Bool = false
61 |
62 | func speedManager(_ manager: SpeedManagerModule.SpeedManager, didUpdateSpeed speed: Double, speedAccuracy: Double) {
63 | didUpdateSpeed = true
64 |
65 | if expectation != nil {
66 | self.speed = speed
67 | self.speedAccuracy = speedAccuracy
68 | }
69 | expectation?.fulfill()
70 | expectation = nil
71 | }
72 |
73 | func speedManager(_ manager: SpeedManagerModule.SpeedManager, didFailWithError error: Error) {
74 | didFailWithError = true
75 | }
76 |
77 | func speedManager(_ speedManager: SpeedManagerModule.SpeedManager, didUpdateAuthorizationStatus status: SpeedManagerModule.SpeedManagerAuthorizationStatus) {
78 | didUpdateAuthorizationStatus = true
79 | }
80 |
81 | func speedManagerDidFailWithLocationServicesUnavailable(_ speedManager: SpeedManagerModule.SpeedManager) {
82 | speedManagerDidFailWithLocationServicesUnavailable = true
83 | }
84 |
85 | init(testCase: XCTestCase) {
86 | self.testCase = testCase
87 | }
88 |
89 | func expectSpeed() {
90 | expectation = testCase.expectation(description: "Expect speed")
91 | }
92 | }
93 |
94 |
95 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | [](https://github.com/apple/swift-package-manager)
5 | [](https://swiftpackageindex.com/ezefranca/SpeedManagerModule)
6 | [](https://swiftpackageindex.com/ezefranca/SpeedManagerModule)
7 | 
8 | [![License][license-image]][license-url]
9 | [](http://twitter.com/ezefranca)
10 |
11 | # SpeedManagerModule
12 | > Simple Speedometer class to iOS and WatchOS.
13 |
14 | Measure the speed using an iPhone or Apple Watch.
15 |
16 |
17 | https://github.com/user-attachments/assets/fbee0a69-a993-4de2-aebb-9459533b0800
18 |
19 | > [!NOTE]
20 | > The Demo UI was created using [LidorFadida](https://github.com/LidorFadida/) package [SpeedometerSwiftUI](https://github.com/LidorFadida/SpeedometerSwiftUI)
21 |
22 | ### Motivation
23 |
24 | I like to measure my speed inside trains and buses. When I was searching for a speedometer app, the majority of them were ugly, with tons of ads. I was searching for an Apple Watch Speedometer with complications, iOS App with Widgets and did not found. Because of that I decided to create my own app. First thing was measure speed using `CLLocationManager`.
25 |
26 | ## Installation
27 |
28 | The Swift Package Manager is the easiest way to install and manage SpeedManagerModule as a dependency.
29 | Simply add SpeedManagerModule to your dependencies in your Package.swift file:
30 |
31 | ```swift
32 | dependencies: [
33 | .package(url: "https://github.com/ezefranca/SpeedManagerModule.git")
34 | ]
35 | ```
36 |
37 | ### Update Info.plist
38 |
39 | Add the correct permission descriptions
40 |
41 | ```xml
42 | NSLocationAlwaysAndWhenInUseUsageDescription
43 | Your description why you should use NSLocationAlwaysAndWhenInUseUsageDescription
44 | NSLocationAlwaysUsageDescription
45 | Your description why you should use NSLocationAlwaysAndWhenInUseUsageDescription
46 | NSLocationWhenInUseUsageDescription
47 | Your description why you should use NSLocationAlwaysAndWhenInUseUsageDescription
48 | ```
49 |
50 | Add the background location updates in xcode
51 |
52 | 
53 |
54 | Or add the info to the Info.plist
55 |
56 | ```xml
57 | UIBackgroundModes
58 |
59 | location
60 |
61 | ```
62 |
63 | ## Usage example
64 |
65 | ### @StateObject
66 |
67 | ```swift
68 | import SwiftUI
69 |
70 | struct ContentView: View {
71 |
72 | @StateObject var speedManager = SpeedManager(.kilometersPerHour)
73 |
74 | var body: some View {
75 | VStack {
76 | switch speedManager.authorizationStatus {
77 | case .authorized:
78 | Text("Your current speed is:")
79 | Text("\(speedManager.speed)")
80 | default:
81 | Spacer()
82 | }
83 | }
84 | }
85 | }
86 | ```
87 |
88 | ### Using Delegates
89 |
90 | ```swift
91 | import UIKit
92 |
93 | class SpeedViewController: UIViewController {
94 |
95 | var speedManager = SpeedManager(.kilometersPerHour)
96 |
97 | override func viewDidLoad() {
98 | super.viewDidLoad()
99 | self.speedManager.delegate = self
100 | self.speedManager.startUpdatingSpeed()
101 | }
102 | }
103 |
104 | extension SpeedViewController: SpeedManagerDelegate {
105 |
106 | func speedManager(_ manager: SpeedManager, didUpdateSpeed speed: Double, speedAccuracy: Double) {
107 | // Update UI with the current speed and accuracy
108 | }
109 |
110 | func speedManager(_ manager: SpeedManager, didFailWithError error: Error) {
111 | // Handle error
112 | }
113 |
114 | func speedManager(_ speedManager: SpeedManager, didUpdateAuthorizationStatus status: SpeedManagerAuthorizationStatus) {
115 | // Handle authorization status update
116 | }
117 |
118 | func speedManagerDidFailWithLocationServicesUnavailable(_ speedManager: SpeedManager) {
119 | // Handle location services unavailable
120 | }
121 | }
122 | ```
123 |
124 | ### Changing Unit
125 |
126 | Just choose the unit during the class init.
127 |
128 | ```swift
129 | var speedManagerKmh = SpeedManager(.kilometersPerHour)
130 | var speedManagerMs = SpeedManager(.metersPerSecond)
131 | var speedManagerMph = SpeedManager(.milesPerHour)
132 | ```
133 |
134 | ### Demo
135 |
136 | Check the `Demo` folder to see it in action.
137 |
138 |
139 | ## Meta
140 |
141 | @ezefranca – [@ezefranca](https://twitter.com/ezefranca)
142 |
143 | Distributed under the MIT license. See `LICENSE` for more information.
144 |
145 | [https://github.com/ezefranca/SpeedManagerModule](https://github.com/ezefranca/SpeedManagerModule)
146 |
147 | [swift-image]:https://img.shields.io/badge/swift-5.0-orange.svg
148 | [swift-url]: https://swift.org/
149 | [license-image]: https://img.shields.io/badge/License-MIT-blue.svg
150 | [license-url]: https://github.com/git/git-scm.com/blob/main/MIT-LICENSE.txt
151 |
--------------------------------------------------------------------------------
/Sources/SpeedManagerModule/SpeedManager.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import CoreLocation
3 |
4 | /// A class that manages the monitoring and updating of speed using CoreLocation.
5 | public class SpeedManager: NSObject, ObservableObject, SpeedManagerTrigger {
6 |
7 | // MARK: - Properties
8 |
9 | /// The CoreLocation manager used to get location updates.
10 | private let locationManager = CLLocationManager()
11 |
12 | /// The unit of speed to be used.
13 | private var speedUnit: SpeedManagerUnit
14 |
15 | /// The trigger for starting speed updates.
16 | private var trigger: SpeedManagerTrigger?
17 |
18 | /// Indicates whether background location updates are allowed.
19 | private var allowsBackgroundLocationUpdates: Bool
20 |
21 | /// The delegate to receive updates from the SpeedManager.
22 | public weak var delegate: SpeedManagerDelegate?
23 |
24 | /// The current authorization status for location services.
25 | @Published public private(set) var authorizationStatus: SpeedManagerAuthorizationStatus = .notDetermined {
26 | didSet {
27 | DispatchQueue.main.async {
28 | self.delegate?.speedManager(self, didUpdateAuthorizationStatus: self.authorizationStatus)
29 | }
30 | }
31 | }
32 |
33 | /// The current speed.
34 | @Published public var speed: Double = 0 {
35 | didSet {
36 | DispatchQueue.main.async {
37 | self.delegate?.speedManager(self, didUpdateSpeed: self.speed, speedAccuracy: self.speedAccuracy)
38 | }
39 | }
40 | }
41 |
42 | /// The accuracy of the current speed.
43 | @Published public private(set) var speedAccuracy: Double = 0
44 |
45 | // MARK: - Initializer
46 |
47 | /// Initializes a new SpeedManager.
48 | /// - Parameters:
49 | /// - speedUnit: The unit of speed measurement.
50 | /// - trigger: An optional trigger for starting speed updates. If nil, the SpeedManager will trigger itself.
51 | /// - allowsBackgroundLocationUpdates: A Boolean value indicating whether background location updates are allowed.
52 | public init(speedUnit: SpeedManagerUnit,
53 | trigger: SpeedManagerTrigger? = nil,
54 | allowsBackgroundLocationUpdates: Bool = false) {
55 |
56 | self.speedUnit = speedUnit
57 | self.allowsBackgroundLocationUpdates = allowsBackgroundLocationUpdates
58 | super.init()
59 | self.trigger = trigger ?? self
60 |
61 | self.locationManager.delegate = self
62 | self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
63 | self.locationManager.distanceFilter = kCLHeadingFilterNone
64 |
65 | self.locationManager.requestAlwaysAuthorization()
66 | }
67 |
68 | // MARK: - Public Methods
69 |
70 | /// Starts updating the speed.
71 | public func startUpdatingSpeed() {
72 | trigger?.startMonitoringSpeed()
73 | }
74 |
75 | /// Starts monitoring the speed.
76 | public func startMonitoringSpeed() {
77 | switch authorizationStatus {
78 | case .authorized:
79 | if allowsBackgroundLocationUpdates {
80 | locationManager.allowsBackgroundLocationUpdates = true
81 | }
82 | locationManager.startUpdatingLocation()
83 | case .notDetermined:
84 | locationManager.requestAlwaysAuthorization()
85 | case .denied:
86 | DispatchQueue.main.async {
87 | self.delegate?.speedManagerDidFailWithLocationServicesUnavailable(self)
88 | }
89 | }
90 | }
91 | }
92 |
93 | // MARK: - CLLocationManagerDelegate
94 |
95 | extension SpeedManager: CLLocationManagerDelegate {
96 |
97 | /// Called when the authorization status changes.
98 | /// - Parameter manager: The location manager reporting the change.
99 | public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
100 | switch manager.authorizationStatus {
101 | case .authorizedWhenInUse, .authorizedAlways:
102 | authorizationStatus = .authorized
103 | locationManager.requestLocation()
104 | case .notDetermined:
105 | authorizationStatus = .notDetermined
106 | manager.requestWhenInUseAuthorization()
107 | default:
108 | authorizationStatus = .denied
109 | }
110 |
111 | startMonitoringSpeed()
112 | }
113 |
114 | /// Called when new location data is available.
115 | /// - Parameters:
116 | /// - manager: The location manager providing the data.
117 | /// - locations: An array of new location data objects.
118 | public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
119 | guard let lastLocation = locations.last else { return }
120 |
121 | let currentSpeed = lastLocation.speed
122 | speed = currentSpeed >= 0 ? currentSpeed * speedUnit.rawValue : .nan
123 | speedAccuracy = lastLocation.speedAccuracy
124 |
125 | locationManager.requestLocation()
126 | }
127 |
128 | /// Called when the location manager encounters an error.
129 | /// - Parameters:
130 | /// - manager: The location manager reporting the error.
131 | /// - error: The error encountered by the location manager.
132 | public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
133 | DispatchQueue.main.async {
134 | self.delegate?.speedManager(self, didFailWithError: error)
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Demo/Demo/Demo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 63;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 22A60BA5295082AC0053DE68 /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22A60BA4295082AC0053DE68 /* DemoApp.swift */; };
11 | 22A60BA7295082AC0053DE68 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22A60BA6295082AC0053DE68 /* ContentView.swift */; };
12 | 22A60BA9295082AD0053DE68 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 22A60BA8295082AD0053DE68 /* Assets.xcassets */; };
13 | 22A60BAD295082AD0053DE68 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 22A60BAC295082AD0053DE68 /* Preview Assets.xcassets */; };
14 | 22A790752952196400BD997E /* SpeedManagerModule in Frameworks */ = {isa = PBXBuildFile; productRef = 22A790742952196400BD997E /* SpeedManagerModule */; };
15 | 453C897B2C4FACF30094C2FC /* Content in Frameworks */ = {isa = PBXBuildFile; productRef = 453C897A2C4FACF30094C2FC /* Content */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | 22A60BA1295082AC0053DE68 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
20 | 22A60BA4295082AC0053DE68 /* DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoApp.swift; sourceTree = ""; };
21 | 22A60BA6295082AC0053DE68 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
22 | 22A60BA8295082AD0053DE68 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23 | 22A60BAA295082AD0053DE68 /* Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Demo.entitlements; sourceTree = ""; };
24 | 22A60BAC295082AD0053DE68 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
25 | 22A60BB8295083AC0053DE68 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
26 | 22A790722952195B00BD997E /* SpeedManagerModule */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = SpeedManagerModule; path = ../..; sourceTree = ""; };
27 | /* End PBXFileReference section */
28 |
29 | /* Begin PBXFrameworksBuildPhase section */
30 | 22A60B9E295082AC0053DE68 /* Frameworks */ = {
31 | isa = PBXFrameworksBuildPhase;
32 | buildActionMask = 2147483647;
33 | files = (
34 | 453C897B2C4FACF30094C2FC /* Content in Frameworks */,
35 | 22A790752952196400BD997E /* SpeedManagerModule in Frameworks */,
36 | );
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXFrameworksBuildPhase section */
40 |
41 | /* Begin PBXGroup section */
42 | 22A60B98295082AC0053DE68 = {
43 | isa = PBXGroup;
44 | children = (
45 | 22A790712952195B00BD997E /* Packages */,
46 | 22A60BA3295082AC0053DE68 /* Demo */,
47 | 22A60BA2295082AC0053DE68 /* Products */,
48 | 22A790732952196400BD997E /* Frameworks */,
49 | );
50 | sourceTree = "";
51 | };
52 | 22A60BA2295082AC0053DE68 /* Products */ = {
53 | isa = PBXGroup;
54 | children = (
55 | 22A60BA1295082AC0053DE68 /* Demo.app */,
56 | );
57 | name = Products;
58 | sourceTree = "";
59 | };
60 | 22A60BA3295082AC0053DE68 /* Demo */ = {
61 | isa = PBXGroup;
62 | children = (
63 | 22A60BB8295083AC0053DE68 /* Info.plist */,
64 | 22A60BA4295082AC0053DE68 /* DemoApp.swift */,
65 | 22A60BA6295082AC0053DE68 /* ContentView.swift */,
66 | 22A60BA8295082AD0053DE68 /* Assets.xcassets */,
67 | 22A60BAA295082AD0053DE68 /* Demo.entitlements */,
68 | 22A60BAB295082AD0053DE68 /* Preview Content */,
69 | );
70 | path = Demo;
71 | sourceTree = "";
72 | };
73 | 22A60BAB295082AD0053DE68 /* Preview Content */ = {
74 | isa = PBXGroup;
75 | children = (
76 | 22A60BAC295082AD0053DE68 /* Preview Assets.xcassets */,
77 | );
78 | path = "Preview Content";
79 | sourceTree = "";
80 | };
81 | 22A790712952195B00BD997E /* Packages */ = {
82 | isa = PBXGroup;
83 | children = (
84 | 22A790722952195B00BD997E /* SpeedManagerModule */,
85 | );
86 | name = Packages;
87 | sourceTree = "";
88 | };
89 | 22A790732952196400BD997E /* Frameworks */ = {
90 | isa = PBXGroup;
91 | children = (
92 | );
93 | name = Frameworks;
94 | sourceTree = "";
95 | };
96 | /* End PBXGroup section */
97 |
98 | /* Begin PBXNativeTarget section */
99 | 22A60BA0295082AC0053DE68 /* Demo */ = {
100 | isa = PBXNativeTarget;
101 | buildConfigurationList = 22A60BB0295082AD0053DE68 /* Build configuration list for PBXNativeTarget "Demo" */;
102 | buildPhases = (
103 | 22A60B9D295082AC0053DE68 /* Sources */,
104 | 22A60B9E295082AC0053DE68 /* Frameworks */,
105 | 22A60B9F295082AC0053DE68 /* Resources */,
106 | );
107 | buildRules = (
108 | );
109 | dependencies = (
110 | );
111 | name = Demo;
112 | packageProductDependencies = (
113 | 22A790742952196400BD997E /* SpeedManagerModule */,
114 | 453C897A2C4FACF30094C2FC /* Content */,
115 | );
116 | productName = Demo;
117 | productReference = 22A60BA1295082AC0053DE68 /* Demo.app */;
118 | productType = "com.apple.product-type.application";
119 | };
120 | /* End PBXNativeTarget section */
121 |
122 | /* Begin PBXProject section */
123 | 22A60B99295082AC0053DE68 /* Project object */ = {
124 | isa = PBXProject;
125 | attributes = {
126 | BuildIndependentTargetsInParallel = 1;
127 | LastSwiftUpdateCheck = 1410;
128 | LastUpgradeCheck = 1410;
129 | TargetAttributes = {
130 | 22A60BA0295082AC0053DE68 = {
131 | CreatedOnToolsVersion = 14.1;
132 | };
133 | };
134 | };
135 | buildConfigurationList = 22A60B9C295082AC0053DE68 /* Build configuration list for PBXProject "Demo" */;
136 | compatibilityVersion = "Xcode 15.3";
137 | developmentRegion = en;
138 | hasScannedForEncodings = 0;
139 | knownRegions = (
140 | en,
141 | Base,
142 | );
143 | mainGroup = 22A60B98295082AC0053DE68;
144 | packageReferences = (
145 | 453C89792C4FACF30094C2FC /* XCRemoteSwiftPackageReference "SpeedometerSwiftUI" */,
146 | );
147 | productRefGroup = 22A60BA2295082AC0053DE68 /* Products */;
148 | projectDirPath = "";
149 | projectRoot = "";
150 | targets = (
151 | 22A60BA0295082AC0053DE68 /* Demo */,
152 | );
153 | };
154 | /* End PBXProject section */
155 |
156 | /* Begin PBXResourcesBuildPhase section */
157 | 22A60B9F295082AC0053DE68 /* Resources */ = {
158 | isa = PBXResourcesBuildPhase;
159 | buildActionMask = 2147483647;
160 | files = (
161 | 22A60BAD295082AD0053DE68 /* Preview Assets.xcassets in Resources */,
162 | 22A60BA9295082AD0053DE68 /* Assets.xcassets in Resources */,
163 | );
164 | runOnlyForDeploymentPostprocessing = 0;
165 | };
166 | /* End PBXResourcesBuildPhase section */
167 |
168 | /* Begin PBXSourcesBuildPhase section */
169 | 22A60B9D295082AC0053DE68 /* Sources */ = {
170 | isa = PBXSourcesBuildPhase;
171 | buildActionMask = 2147483647;
172 | files = (
173 | 22A60BA7295082AC0053DE68 /* ContentView.swift in Sources */,
174 | 22A60BA5295082AC0053DE68 /* DemoApp.swift in Sources */,
175 | );
176 | runOnlyForDeploymentPostprocessing = 0;
177 | };
178 | /* End PBXSourcesBuildPhase section */
179 |
180 | /* Begin XCBuildConfiguration section */
181 | 22A60BAE295082AD0053DE68 /* Debug */ = {
182 | isa = XCBuildConfiguration;
183 | buildSettings = {
184 | ALWAYS_SEARCH_USER_PATHS = NO;
185 | CLANG_ANALYZER_NONNULL = YES;
186 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
187 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
188 | CLANG_ENABLE_MODULES = YES;
189 | CLANG_ENABLE_OBJC_ARC = YES;
190 | CLANG_ENABLE_OBJC_WEAK = YES;
191 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
192 | CLANG_WARN_BOOL_CONVERSION = YES;
193 | CLANG_WARN_COMMA = YES;
194 | CLANG_WARN_CONSTANT_CONVERSION = YES;
195 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
196 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
197 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
198 | CLANG_WARN_EMPTY_BODY = YES;
199 | CLANG_WARN_ENUM_CONVERSION = YES;
200 | CLANG_WARN_INFINITE_RECURSION = YES;
201 | CLANG_WARN_INT_CONVERSION = YES;
202 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
203 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
204 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
205 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
206 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
207 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
208 | CLANG_WARN_STRICT_PROTOTYPES = YES;
209 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
210 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
211 | CLANG_WARN_UNREACHABLE_CODE = YES;
212 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
213 | COPY_PHASE_STRIP = NO;
214 | DEBUG_INFORMATION_FORMAT = dwarf;
215 | ENABLE_STRICT_OBJC_MSGSEND = YES;
216 | ENABLE_TESTABILITY = YES;
217 | GCC_C_LANGUAGE_STANDARD = gnu11;
218 | GCC_DYNAMIC_NO_PIC = NO;
219 | GCC_NO_COMMON_BLOCKS = YES;
220 | GCC_OPTIMIZATION_LEVEL = 0;
221 | GCC_PREPROCESSOR_DEFINITIONS = (
222 | "DEBUG=1",
223 | "$(inherited)",
224 | );
225 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
226 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
227 | GCC_WARN_UNDECLARED_SELECTOR = YES;
228 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
229 | GCC_WARN_UNUSED_FUNCTION = YES;
230 | GCC_WARN_UNUSED_VARIABLE = YES;
231 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
232 | MTL_FAST_MATH = YES;
233 | ONLY_ACTIVE_ARCH = YES;
234 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
235 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
236 | };
237 | name = Debug;
238 | };
239 | 22A60BAF295082AD0053DE68 /* Release */ = {
240 | isa = XCBuildConfiguration;
241 | buildSettings = {
242 | ALWAYS_SEARCH_USER_PATHS = NO;
243 | CLANG_ANALYZER_NONNULL = YES;
244 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
245 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
246 | CLANG_ENABLE_MODULES = YES;
247 | CLANG_ENABLE_OBJC_ARC = YES;
248 | CLANG_ENABLE_OBJC_WEAK = YES;
249 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
250 | CLANG_WARN_BOOL_CONVERSION = YES;
251 | CLANG_WARN_COMMA = YES;
252 | CLANG_WARN_CONSTANT_CONVERSION = YES;
253 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
254 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
255 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
256 | CLANG_WARN_EMPTY_BODY = YES;
257 | CLANG_WARN_ENUM_CONVERSION = YES;
258 | CLANG_WARN_INFINITE_RECURSION = YES;
259 | CLANG_WARN_INT_CONVERSION = YES;
260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
264 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
265 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
266 | CLANG_WARN_STRICT_PROTOTYPES = YES;
267 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
268 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
269 | CLANG_WARN_UNREACHABLE_CODE = YES;
270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
271 | COPY_PHASE_STRIP = NO;
272 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
273 | ENABLE_NS_ASSERTIONS = NO;
274 | ENABLE_STRICT_OBJC_MSGSEND = YES;
275 | GCC_C_LANGUAGE_STANDARD = gnu11;
276 | GCC_NO_COMMON_BLOCKS = YES;
277 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
278 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
279 | GCC_WARN_UNDECLARED_SELECTOR = YES;
280 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
281 | GCC_WARN_UNUSED_FUNCTION = YES;
282 | GCC_WARN_UNUSED_VARIABLE = YES;
283 | MTL_ENABLE_DEBUG_INFO = NO;
284 | MTL_FAST_MATH = YES;
285 | SWIFT_COMPILATION_MODE = wholemodule;
286 | SWIFT_OPTIMIZATION_LEVEL = "-O";
287 | };
288 | name = Release;
289 | };
290 | 22A60BB1295082AD0053DE68 /* Debug */ = {
291 | isa = XCBuildConfiguration;
292 | buildSettings = {
293 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
294 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
295 | CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements;
296 | CODE_SIGN_STYLE = Automatic;
297 | CURRENT_PROJECT_VERSION = 1;
298 | DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\"";
299 | DEVELOPMENT_TEAM = 54JDK55DR5;
300 | ENABLE_HARDENED_RUNTIME = YES;
301 | ENABLE_PREVIEWS = YES;
302 | GENERATE_INFOPLIST_FILE = YES;
303 | INFOPLIST_FILE = Demo/Info.plist;
304 | INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "NSLocationAlwaysAndWhenInUseUsageDescription Example";
305 | INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "NSLocationAlwaysUsageDescription Example";
306 | INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "NSLocationWhenInUseUsageDescription Example";
307 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
308 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
309 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
310 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
311 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
312 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
313 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
314 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
315 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
316 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
317 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
318 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
319 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
320 | MACOSX_DEPLOYMENT_TARGET = 12.4;
321 | MARKETING_VERSION = 1.0;
322 | PRODUCT_BUNDLE_IDENTIFIER = com.ezefranca.Demo;
323 | PRODUCT_NAME = "$(TARGET_NAME)";
324 | SDKROOT = auto;
325 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
326 | SUPPORTS_MACCATALYST = NO;
327 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
328 | SWIFT_EMIT_LOC_STRINGS = YES;
329 | SWIFT_VERSION = 5.0;
330 | TARGETED_DEVICE_FAMILY = "1,2,3";
331 | };
332 | name = Debug;
333 | };
334 | 22A60BB2295082AD0053DE68 /* Release */ = {
335 | isa = XCBuildConfiguration;
336 | buildSettings = {
337 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
338 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
339 | CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements;
340 | CODE_SIGN_STYLE = Automatic;
341 | CURRENT_PROJECT_VERSION = 1;
342 | DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\"";
343 | DEVELOPMENT_TEAM = 54JDK55DR5;
344 | ENABLE_HARDENED_RUNTIME = YES;
345 | ENABLE_PREVIEWS = YES;
346 | GENERATE_INFOPLIST_FILE = YES;
347 | INFOPLIST_FILE = Demo/Info.plist;
348 | INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "NSLocationAlwaysAndWhenInUseUsageDescription Example";
349 | INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "NSLocationAlwaysUsageDescription Example";
350 | INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "NSLocationWhenInUseUsageDescription Example";
351 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
352 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
353 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
354 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES;
355 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES;
356 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
357 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
358 | "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
359 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
360 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
361 | IPHONEOS_DEPLOYMENT_TARGET = 17.0;
362 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
363 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
364 | MACOSX_DEPLOYMENT_TARGET = 12.4;
365 | MARKETING_VERSION = 1.0;
366 | PRODUCT_BUNDLE_IDENTIFIER = com.ezefranca.Demo;
367 | PRODUCT_NAME = "$(TARGET_NAME)";
368 | SDKROOT = auto;
369 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx";
370 | SUPPORTS_MACCATALYST = NO;
371 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
372 | SWIFT_EMIT_LOC_STRINGS = YES;
373 | SWIFT_VERSION = 5.0;
374 | TARGETED_DEVICE_FAMILY = "1,2,3";
375 | };
376 | name = Release;
377 | };
378 | /* End XCBuildConfiguration section */
379 |
380 | /* Begin XCConfigurationList section */
381 | 22A60B9C295082AC0053DE68 /* Build configuration list for PBXProject "Demo" */ = {
382 | isa = XCConfigurationList;
383 | buildConfigurations = (
384 | 22A60BAE295082AD0053DE68 /* Debug */,
385 | 22A60BAF295082AD0053DE68 /* Release */,
386 | );
387 | defaultConfigurationIsVisible = 0;
388 | defaultConfigurationName = Release;
389 | };
390 | 22A60BB0295082AD0053DE68 /* Build configuration list for PBXNativeTarget "Demo" */ = {
391 | isa = XCConfigurationList;
392 | buildConfigurations = (
393 | 22A60BB1295082AD0053DE68 /* Debug */,
394 | 22A60BB2295082AD0053DE68 /* Release */,
395 | );
396 | defaultConfigurationIsVisible = 0;
397 | defaultConfigurationName = Release;
398 | };
399 | /* End XCConfigurationList section */
400 |
401 | /* Begin XCRemoteSwiftPackageReference section */
402 | 453C89792C4FACF30094C2FC /* XCRemoteSwiftPackageReference "SpeedometerSwiftUI" */ = {
403 | isa = XCRemoteSwiftPackageReference;
404 | repositoryURL = "https://github.com/LidorFadida/SpeedometerSwiftUI";
405 | requirement = {
406 | branch = main;
407 | kind = branch;
408 | };
409 | };
410 | /* End XCRemoteSwiftPackageReference section */
411 |
412 | /* Begin XCSwiftPackageProductDependency section */
413 | 22A790742952196400BD997E /* SpeedManagerModule */ = {
414 | isa = XCSwiftPackageProductDependency;
415 | productName = SpeedManagerModule;
416 | };
417 | 453C897A2C4FACF30094C2FC /* Content */ = {
418 | isa = XCSwiftPackageProductDependency;
419 | package = 453C89792C4FACF30094C2FC /* XCRemoteSwiftPackageReference "SpeedometerSwiftUI" */;
420 | productName = Content;
421 | };
422 | /* End XCSwiftPackageProductDependency section */
423 | };
424 | rootObject = 22A60B99295082AC0053DE68 /* Project object */;
425 | }
426 |
--------------------------------------------------------------------------------