diff --git a/OTPKit/Features/MapExtension/MapMarkingView.swift b/OTPKit/Features/MapExtension/MapMarkingView.swift new file mode 100644 index 0000000..bc5671a --- /dev/null +++ b/OTPKit/Features/MapExtension/MapMarkingView.swift @@ -0,0 +1,53 @@ +// +// MapMarkingView.swift +// OTPKit +// +// Created by Hilmy Veradin on 18/07/24. +// + +import SwiftUI + +public struct MapMarkingView: View { + @ObservedObject private var locationManagerService = LocationManagerService.shared + + public init() {} + public var body: some View { + VStack { + Spacer() + + Text("Tap on the map to add a pin.") + .padding(16) + .background(.regularMaterial) + .cornerRadius(16) + + HStack(spacing: 16) { + Button { + locationManagerService.toggleMapMarkingMode(false) + locationManagerService.selectCoordinate() + } label: { + Text("Cancel") + .padding(8) + .frame(maxWidth: .infinity) + } + .buttonStyle(.bordered) + + Button { + locationManagerService.toggleMapMarkingMode(false) + locationManagerService.selectCoordinate() + } label: { + Text("Add Pin") + .padding(8) + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) + } + .frame(maxWidth: .infinity) + .padding(16) + } + .padding(.bottom, 24) + } +} + +#Preview { + MapMarkingView() +} diff --git a/OTPKit/Features/OriginDestination/OriginDestinationSheetEnvironment.swift b/OTPKit/Features/OriginDestination/OriginDestinationSheetEnvironment.swift index b3ddc2a..7eede29 100644 --- a/OTPKit/Features/OriginDestination/OriginDestinationSheetEnvironment.swift +++ b/OTPKit/Features/OriginDestination/OriginDestinationSheetEnvironment.swift @@ -8,21 +8,11 @@ import Foundation import SwiftUI -/// OriginDestinationSheetState responsible for managing states of the shown `OriginDestinationSheetView` -/// - Enums: -/// - origin: This manage origin state of the trip planner -/// - destination: This manage destination state of the trip planner -public enum OriginDestinationSheetState { - case origin - case destination -} - /// OriginDestinationSheetEnvironment responsible for manage the environment of `OriginDestination` features /// - sheetState: responsible for managing shown sheet in `OriginDestinationView` /// - selectedValue: responsible for managing selected value when user taped the list in `OriginDestinationSheetView` public final class OriginDestinationSheetEnvironment: ObservableObject { @Published public var isSheetOpened = false - @Published public var sheetState: OriginDestinationSheetState = .origin @Published public var selectedValue: String = "" // This responsible for showing favorite locations and recent locations in sheets diff --git a/OTPKit/Features/OriginDestination/OriginDestinationView.swift b/OTPKit/Features/OriginDestination/OriginDestinationView.swift index d5ec069..18c4ae4 100644 --- a/OTPKit/Features/OriginDestination/OriginDestinationView.swift +++ b/OTPKit/Features/OriginDestination/OriginDestinationView.swift @@ -12,6 +12,7 @@ import SwiftUI /// It consists a list of Origin and Destination along with the `MapKit` public struct OriginDestinationView: View { @EnvironmentObject private var sheetEnvironment: OriginDestinationSheetEnvironment + @ObservedObject private var locationManagerService = LocationManagerService.shared @State private var isSheetOpened = false // Public Initializer @@ -22,7 +23,7 @@ public struct OriginDestinationView: View { List { Button(action: { sheetEnvironment.isSheetOpened.toggle() - sheetEnvironment.sheetState = .origin + locationManagerService.originDestinationState = .origin }, label: { HStack(spacing: 16) { Image(systemName: "paperplane.fill") @@ -38,7 +39,7 @@ public struct OriginDestinationView: View { Button(action: { sheetEnvironment.isSheetOpened.toggle() - sheetEnvironment.sheetState = .destination + locationManagerService.originDestinationState = .destination }, label: { HStack(spacing: 16) { Image(systemName: "mappin") @@ -55,6 +56,7 @@ public struct OriginDestinationView: View { .frame(height: 135) .scrollContentBackground(.hidden) .scrollDisabled(true) + .padding(.bottom, 24) } } } diff --git a/OTPKit/Features/OriginDestination/Sheets/AddFavoriteLocationsSheet.swift b/OTPKit/Features/OriginDestination/Sheets/AddFavoriteLocationsSheet.swift index d25f1b4..7079d92 100644 --- a/OTPKit/Features/OriginDestination/Sheets/AddFavoriteLocationsSheet.swift +++ b/OTPKit/Features/OriginDestination/Sheets/AddFavoriteLocationsSheet.swift @@ -11,18 +11,17 @@ import SwiftUI /// Users can search and add their favorite locations public struct AddFavoriteLocationsSheet: View { @Environment(\.dismiss) var dismiss - - @EnvironmentObject private var locationService: LocationService @EnvironmentObject private var sheetEnvironment: OriginDestinationSheetEnvironment + @ObservedObject private var locationManagerService = LocationManagerService.shared + @State private var search = "" - private let userLocation = UserLocationServices.shared.currentLocation - @FocusState private var isSearchActive: Bool + @FocusState private var isSearchFocused: Bool private var filteredCompletions: [Location] { let favorites = sheetEnvironment.favoriteLocations - return locationService.completions.filter { completion in + return locationManagerService.completions.filter { completion in !favorites.contains { favorite in favorite.title == completion.title && favorite.subTitle == completion.subTitle @@ -30,88 +29,106 @@ public struct AddFavoriteLocationsSheet: View { } } - public var body: some View { - VStack { - HStack { - Text("Add favorite location") + private func headerView() -> some View { + HStack { + Text("Add favorite location") + .font(.title2) + .fontWeight(.bold) + Spacer() + Button(action: { + dismiss() + }, label: { + Image(systemName: "xmark.circle.fill") .font(.title2) - .fontWeight(.bold) - Spacer() + .foregroundColor(.gray) + }) + } + .padding() + } + + private func searchView() -> some View { + HStack { + Image(systemName: "magnifyingglass") + TextField("Search for a place", text: $search) + .autocorrectionDisabled() + .focused($isSearchFocused) + } + .padding(.vertical, 8) + .padding(.horizontal, 12) + .background(Color.gray.opacity(0.2)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .padding(.horizontal, 16) + } + + private func currentUserSection() -> some View { + if search.isEmpty, let userLocation = locationManagerService.currentLocation { + AnyView( Button(action: { - dismiss() + switch UserDefaultsServices.shared.saveFavoriteLocationData(data: userLocation) { + case .success: + sheetEnvironment.refreshFavoriteLocations() + dismiss() + case let .failure(error): + print(error) + } }, label: { - Image(systemName: "xmark.circle.fill") - .font(.title2) - .foregroundColor(.gray) + HStack { + VStack(alignment: .leading) { + Text(userLocation.title) + .font(.headline) + Text(userLocation.subTitle) + }.foregroundStyle(Color.black) + + Spacer() + + Image(systemName: "plus") + } + }) - } - .padding() + ) - HStack { - Image(systemName: "magnifyingglass") - TextField("Search for a place", text: $search) - .autocorrectionDisabled() - .focused($isSearchActive) - } - .padding(.vertical, 8) - .padding(.horizontal, 12) - .background(Color.gray.opacity(0.2)) - .clipShape(.rect(cornerRadius: 12)) - .padding(.horizontal, 16) + } else { + AnyView(EmptyView()) + } + } - List { - if search.isEmpty, let userLocation { - Button(action: { - switch UserDefaultsServices.shared.saveFavoriteLocationData(data: userLocation) { - case .success: - sheetEnvironment.refreshFavoriteLocations() - dismiss() - case let .failure(error): - print(error) - } - }, label: { - HStack { - VStack(alignment: .leading) { - Text(userLocation.title) - .font(.headline) - Text(userLocation.subTitle) - }.foregroundStyle(Color.black) - - Spacer() - - Image(systemName: "plus") - } - - }) + private func searchedResultsSection() -> some View { + ForEach(filteredCompletions) { location in + Button(action: { + switch UserDefaultsServices.shared.saveFavoriteLocationData(data: location) { + case .success: + sheetEnvironment.refreshFavoriteLocations() + dismiss() + case let .failure(error): + print(error) } + }, label: { + HStack { + VStack(alignment: .leading) { + Text(location.title) + .font(.headline) + Text(location.subTitle) + }.foregroundStyle(Color.black) + + Spacer() - ForEach(filteredCompletions) { location in - Button(action: { - switch UserDefaultsServices.shared.saveFavoriteLocationData(data: location) { - case .success: - sheetEnvironment.refreshFavoriteLocations() - dismiss() - case let .failure(error): - print(error) - } - }, label: { - HStack { - VStack(alignment: .leading) { - Text(location.title) - .font(.headline) - Text(location.subTitle) - }.foregroundStyle(Color.black) - - Spacer() - - Image(systemName: "plus") - } - - }) + Image(systemName: "plus") } + + }) + } + } + + public var body: some View { + VStack { + headerView() + searchView() + List { + currentUserSection() + searchedResultsSection() } .onChange(of: search) { _, searchValue in - locationService.update(queryFragment: searchValue) + locationManagerService.updateQuery(queryFragment: searchValue) } } } diff --git a/OTPKit/Features/OriginDestination/Sheets/OriginDestinationSheetView.swift b/OTPKit/Features/OriginDestination/Sheets/OriginDestinationSheetView.swift index 9cc9a7b..0710845 100644 --- a/OTPKit/Features/OriginDestination/Sheets/OriginDestinationSheetView.swift +++ b/OTPKit/Features/OriginDestination/Sheets/OriginDestinationSheetView.swift @@ -11,12 +11,10 @@ public struct OriginDestinationSheetView: View { @Environment(\.dismiss) var dismiss @EnvironmentObject var sheetEnvironment: OriginDestinationSheetEnvironment - @StateObject private var locationService = LocationService() + @ObservedObject private var locationManagerService = LocationManagerService.shared @State private var search: String = "" - private let userLocation = UserLocationServices.shared.currentLocation - // Sheet States @State private var isAddSavedLocationsSheetOpen = false @State private var isFavoriteLocationSheetOpen = false @@ -116,7 +114,6 @@ public struct OriginDestinationSheetView: View { }) .sheet(isPresented: $isAddSavedLocationsSheetOpen, content: { AddFavoriteLocationsSheet() - .environmentObject(locationService) .environmentObject(sheetEnvironment) }) .sheet(isPresented: $isFavoriteLocationSheetOpen, content: { @@ -167,7 +164,7 @@ public struct OriginDestinationSheetView: View { private func searchResultsSection() -> some View { Group { - ForEach(locationService.completions) { location in + ForEach(locationManagerService.completions) { location in Button(action: { switch UserDefaultsServices.shared.saveRecentLocations(data: location) { case .success: @@ -190,7 +187,7 @@ public struct OriginDestinationSheetView: View { private func currentUserSection() -> some View { Group { - if let userLocation { + if let userLocation = locationManagerService.currentLocation { Button(action: { switch UserDefaultsServices.shared.saveRecentLocations(data: userLocation) { case .success: @@ -213,6 +210,19 @@ public struct OriginDestinationSheetView: View { } } + private func selectLocationBasedOnMap() -> some View { + Button(action: { + locationManagerService.toggleMapMarkingMode(true) + dismiss() + }, label: { + HStack { + Image(systemName: "mappin") + Text("Choose on Map") + } + }) + .buttonStyle(PlainButtonStyle()) + } + public var body: some View { VStack { headerView() @@ -225,6 +235,7 @@ public struct OriginDestinationSheetView: View { if search.isEmpty, isSearchFocused { currentUserSection() } else if search.isEmpty { + selectLocationBasedOnMap() favoritesSection() recentsSection() } else { @@ -232,7 +243,7 @@ public struct OriginDestinationSheetView: View { } } .onChange(of: search) { _, searchValue in - locationService.update(queryFragment: searchValue) + locationManagerService.updateQuery(queryFragment: searchValue) } Spacer() diff --git a/OTPKit/Models/OriginDestination/OriginDestinationState.swift b/OTPKit/Models/OriginDestination/OriginDestinationState.swift new file mode 100644 index 0000000..385e2c4 --- /dev/null +++ b/OTPKit/Models/OriginDestination/OriginDestinationState.swift @@ -0,0 +1,17 @@ +// +// OriginDestinationState.swift +// OTPKit +// +// Created by Hilmy Veradin on 18/07/24. +// + +import Foundation + +/// Responsible for managing origin or destination state +/// - Enums: +/// - origin: This manage origin state of the trip planner +/// - destination: This manage destination state of the trip planner +public enum OriginDestinationState { + case origin + case destination +} diff --git a/OTPKit/Services/LocationManagerService.swift b/OTPKit/Services/LocationManagerService.swift new file mode 100644 index 0000000..1b57f2f --- /dev/null +++ b/OTPKit/Services/LocationManagerService.swift @@ -0,0 +1,191 @@ +// +// LocationManagerService.swift +// OTPKit +// +// Created by Hilmy Veradin on 18/07/24. +// + +import Foundation +import MapKit +import SwiftUI + +public final class LocationManagerService: NSObject, ObservableObject { + public static let shared = LocationManagerService() + + // MARK: - Properties + + // Origin Destination + @Published public var originDestinationState: OriginDestinationState = .origin + @Published public var originCoordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0) + @Published public var destinationCoordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0) + + // Location Search + private let completer: MKLocalSearchCompleter + private let debounceInterval: TimeInterval + private var debounceTimer: Timer? + @Published var completions = [Location]() + + // Map Extension + @Published public var selectedMapPoint: [String: MarkerItem?] = [ + "origin": nil, + "destination": nil + ] + + @Published public var isMapMarkingMode = false + + // User Location + @Published var currentLocation: Location? + var locationManager: CLLocationManager = .init() + + // MARK: - Initialization + + override private init() { + completer = MKLocalSearchCompleter() + debounceInterval = 0.3 + super.init() + + completer.delegate = self + locationManager.delegate = self + locationManager.desiredAccuracy = kCLLocationAccuracyBest + } + + deinit { + debounceTimer?.invalidate() + } + + // MARK: - Location Search Methods + + /// Initiates a local search for `queryFragment`. + /// This will be debounced, as set by the `debounceInterval` on the initializer. + /// - Parameter queryFragment: The search term + public func updateQuery(queryFragment: String) { + debounceTimer?.invalidate() + debounceTimer = Timer.scheduledTimer(withTimeInterval: debounceInterval, repeats: false) { [weak self] _ in + guard let self else { return } + completer.resultTypes = .query + completer.queryFragment = queryFragment + } + } + + // MARK: - Map Extension Methods + + public func selectCoordinate() { + switch originDestinationState { + case .origin: + guard let coordinate = selectedMapPoint["origin"]??.item.placemark.coordinate else { return } + originCoordinate = coordinate + case .destination: + guard let coordinate = selectedMapPoint["destination"]??.item.placemark.coordinate else { return } + destinationCoordinate = coordinate + } + } + + public func appendMarker(coordinate: CLLocationCoordinate2D) { + let mapItem = MKMapItem(placemark: .init(coordinate: coordinate)) + let markerItem = MarkerItem(item: mapItem) + switch originDestinationState { + case .origin: + selectedMapPoint["origin"] = markerItem + case .destination: + selectedMapPoint["destination"] = markerItem + } + } + + public func generateMarkers() -> ForEach<[MarkerItem], MarkerItem.ID, Marker> { + ForEach(Array(selectedMapPoint.values.compactMap { $0 }), id: \.id) { markerItem in + Marker(item: markerItem.item) + } + } + + public func toggleMapMarkingMode(_ isMapMarking: Bool) { + // If it's true, then reset the states. When entering the + if isMapMarking { + switch originDestinationState { + case .origin: + selectedMapPoint["origin"] = nil + case .destination: + selectedMapPoint["destination"] = nil + } + } + isMapMarkingMode = isMapMarking + } + + // MARK: - User Location Methods + + public func checkIfLocationServicesIsEnabled() { + DispatchQueue.global().async { + if CLLocationManager.locationServicesEnabled() { + self.checkLocationAuthorization() + } + } + } + + private func checkLocationAuthorization() { + switch locationManager.authorizationStatus { + case .notDetermined: + locationManager.requestWhenInUseAuthorization() + case .restricted, .denied: + // Handle restricted or denied + break + case .authorizedAlways, .authorizedWhenInUse: + locationManager.startUpdatingLocation() + @unknown default: + break + } + } +} + +// MARK: - MKLocalSearchCompleterDelegate + +extension LocationManagerService: MKLocalSearchCompleterDelegate { + public func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { + completions.removeAll() + + for result in completer.results { + let searchRequest = MKLocalSearch.Request(completion: result) + let search = MKLocalSearch(request: searchRequest) + + search.start { [weak self] response, error in + guard let self, let response else { + if let error { + print("Error performing local search: \(error)") + } + return + } + + if let mapItem = response.mapItems.first { + let completion = Location( + title: result.title, + subTitle: result.subtitle, + latitude: mapItem.placemark.coordinate.latitude, + longitude: mapItem.placemark.coordinate.longitude + ) + + DispatchQueue.main.async { + self.completions.append(completion) + } + } + } + } + } +} + +// MARK: - CLLocationManagerDelegate + +extension LocationManagerService: CLLocationManagerDelegate { + public func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + guard let location = locations.last else { return } + DispatchQueue.main.async { + self.currentLocation = Location( + title: "My Location", + subTitle: "Your current location", + latitude: location.coordinate.latitude, + longitude: location.coordinate.longitude + ) + } + } + + public func locationManagerDidChangeAuthorization(_: CLLocationManager) { + checkLocationAuthorization() + } +} diff --git a/OTPKit/Services/LocationServices.swift b/OTPKit/Services/LocationServices.swift deleted file mode 100644 index f6b43b7..0000000 --- a/OTPKit/Services/LocationServices.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// LocationServices.swift -// OTPKitDemo -// -// Created by Hilmy Veradin on 25/06/24. -// - -import Foundation -import MapKit - -/// Manages everything location such as search completer, etc -public final class LocationService: NSObject, ObservableObject, MKLocalSearchCompleterDelegate { - private let completer: MKLocalSearchCompleter - - @Published var completions = [Location]() - - init(completer: MKLocalSearchCompleter = MKLocalSearchCompleter(), debounceInterval: TimeInterval = 0.3) { - self.completer = completer - self.debounceInterval = debounceInterval - super.init() - self.completer.delegate = self - } - - deinit { - debounceTimer?.invalidate() - } - - private let debounceInterval: TimeInterval - - private var debounceTimer: Timer? - - /// Initiates a local search for `queryFragment`. - /// This will be debounced, as set by the `debounceInterval` on the initializer. - /// - Parameter queryFragment: The search term - public func update(queryFragment: String) { - debounceTimer?.invalidate() - debounceTimer = Timer.scheduledTimer(withTimeInterval: debounceInterval, repeats: false) { [weak self] _ in - guard let self else { return } - completer.resultTypes = .query - completer.queryFragment = queryFragment - } - } - - /// completerDidUpdateResults is method that finished the search functionality and update the `completer`. - /// This is required function from `MKLocalSearchCompleterDelegate` - public func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { - completions.removeAll() - - for result in completer.results { - let searchRequest = MKLocalSearch.Request(completion: result) - let search = MKLocalSearch(request: searchRequest) - - search.start { [weak self] response, error in - guard let self, let response else { - if let error { - print("Error performing local search: \(error)") - } - return - } - - if let mapItem = response.mapItems.first { - let completion = Location( - title: result.title, - subTitle: result.subtitle, - latitude: mapItem.placemark.coordinate.latitude, - longitude: mapItem.placemark.coordinate.longitude - ) - - DispatchQueue.main.async { - self.completions.append(completion) - } - } - } - } - } -} diff --git a/OTPKit/Services/MapExtensionServices.swift b/OTPKit/Services/MapExtensionServices.swift deleted file mode 100644 index 73d09eb..0000000 --- a/OTPKit/Services/MapExtensionServices.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// MapExtensionServices.swift -// OTPKit -// -// Created by Hilmy Veradin on 16/07/24. -// - -import Foundation -import MapKit -import SwiftUI - -/// Manage Map extension such as markers, etc -public final class MapExtensionServices: ObservableObject { - public static let shared = MapExtensionServices() - - @Published public var selectedMapPoint: [MarkerItem] = [] - - public func appendMarker(coordinate: CLLocationCoordinate2D) { - let mapItem = MKMapItem(placemark: .init(coordinate: coordinate)) - let markerItem = MarkerItem(item: mapItem) - selectedMapPoint.append(markerItem) - } - - /// Generate MapKit Marker View - public func generateMarkers() -> ForEach<[MarkerItem], MarkerItem.ID, Marker> { - ForEach(selectedMapPoint, id: \.id) { markerItem in - Marker(item: markerItem.item) - } - } -} diff --git a/OTPKit/Services/UserLocationServices.swift b/OTPKit/Services/UserLocationServices.swift deleted file mode 100644 index 0f4cc83..0000000 --- a/OTPKit/Services/UserLocationServices.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// UserLocationServices.swift -// OTPKit -// -// Created by Hilmy Veradin on 06/07/24. -// - -import Foundation -import MapKit - -/// Manages permission, and current users location -public final class UserLocationServices: NSObject, ObservableObject, CLLocationManagerDelegate { - @Published var currentLocation: Location? - - public static let shared = UserLocationServices() - - var locationManager: CLLocationManager = .init() - - override private init() { - super.init() - locationManager.delegate = self - locationManager.desiredAccuracy = kCLLocationAccuracyBest - } - - public func checkIfLocationServicesIsEnabled() { - DispatchQueue.global().async { - if CLLocationManager.locationServicesEnabled() { - self.checkLocationAuthorization() - } - } - } - - private func checkLocationAuthorization() { - switch locationManager.authorizationStatus { - case .notDetermined: - locationManager.requestWhenInUseAuthorization() - case .restricted, .denied: - // Handle restricted or denied - break - case .authorizedAlways, .authorizedWhenInUse: - locationManager.startUpdatingLocation() - @unknown default: - break - } - } - - public func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - guard let location = locations.last else { return } - DispatchQueue.main.async { - self.currentLocation = Location( - title: "My Location", - subTitle: "Your current location", - latitude: location.coordinate.latitude, - longitude: location.coordinate.longitude - ) - } - } - - public func locationManagerDidChangeAuthorization(_: CLLocationManager) { - checkLocationAuthorization() - } -} diff --git a/OTPKitDemo/MapView.swift b/OTPKitDemo/MapView.swift index afc83e4..cb37f21 100644 --- a/OTPKitDemo/MapView.swift +++ b/OTPKitDemo/MapView.swift @@ -11,9 +11,7 @@ import SwiftUI public struct MapView: View { @StateObject private var sheetEnvironment = OriginDestinationSheetEnvironment() - - @StateObject private var userLocationService = UserLocationServices.shared - @StateObject private var mapExtensionService = MapExtensionServices.shared + @ObservedObject private var locationManagerService = LocationManagerService.shared @State private var position: MapCameraPosition = .userLocation(fallback: .automatic) @@ -21,31 +19,39 @@ public struct MapView: View { ZStack { MapReader { proxy in Map(position: $position, interactionModes: .all) { - mapExtensionService + locationManagerService .generateMarkers() } .mapControls { - MapUserLocationButton() - MapPitchToggle() + if !locationManagerService.isMapMarkingMode { + MapUserLocationButton() + MapPitchToggle() + } + } + .onTapGesture { tappedLocation in + if locationManagerService.isMapMarkingMode { + guard let coordinate = proxy.convert(tappedLocation, from: .local) else { return } + locationManagerService.appendMarker(coordinate: coordinate) + } } .sheet(isPresented: $sheetEnvironment.isSheetOpened) { OriginDestinationSheetView() .environmentObject(sheetEnvironment) } - .onTapGesture { tappedLocation in - guard let coordinate = proxy.convert(tappedLocation, from: .local) else { return } - mapExtensionService.appendMarker(coordinate: coordinate) - } } + if locationManagerService.isMapMarkingMode { + MapMarkingView() - VStack { - Spacer() - OriginDestinationView() - .environmentObject(sheetEnvironment) + } else { + VStack { + Spacer() + OriginDestinationView() + .environmentObject(sheetEnvironment) + } } } .onAppear { - userLocationService.checkIfLocationServicesIsEnabled() + locationManagerService.checkIfLocationServicesIsEnabled() } } }