From 6370e7c4fa9fb8b41d99c29ab184de4854472561 Mon Sep 17 00:00:00 2001 From: hilmyveradin Date: Fri, 19 Jul 2024 00:11:17 +0700 Subject: [PATCH 1/4] move all location based services to a new class and add Map Marking View --- .../MapExtension/MapMarkingView.swift | 47 +++++ .../OriginDestinationSheetEnvironment.swift | 10 - .../OriginDestinationView.swift | 6 +- .../Sheets/AddFavoriteLocationsSheet.swift | 169 +++++++++------- .../Sheets/OriginDestinationSheetView.swift | 26 ++- .../OriginDestinationState.swift | 17 ++ OTPKit/Services/LocationManagerService.swift | 190 ++++++++++++++++++ OTPKit/Services/LocationServices.swift | 76 ------- OTPKit/Services/MapExtensionServices.swift | 30 --- OTPKit/Services/UserLocationServices.swift | 62 ------ OTPKitDemo/MapView.swift | 36 ++-- 11 files changed, 391 insertions(+), 278 deletions(-) create mode 100644 OTPKit/Features/MapExtension/MapMarkingView.swift create mode 100644 OTPKit/Models/OriginDestination/OriginDestinationState.swift create mode 100644 OTPKit/Services/LocationManagerService.swift delete mode 100644 OTPKit/Services/LocationServices.swift delete mode 100644 OTPKit/Services/MapExtensionServices.swift delete mode 100644 OTPKit/Services/UserLocationServices.swift diff --git a/OTPKit/Features/MapExtension/MapMarkingView.swift b/OTPKit/Features/MapExtension/MapMarkingView.swift new file mode 100644 index 0000000..fb1cc0c --- /dev/null +++ b/OTPKit/Features/MapExtension/MapMarkingView.swift @@ -0,0 +1,47 @@ +// +// 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() + + VStack(spacing: 16) { + Button(action: { + locationManagerService.toggleMapMarkingMode(false) + locationManagerService.selectCoordinate() + }, label: { + Text("Add Map Location") + }) + .padding(.all) + .background(Color.gray) + .clipShape(.rect(cornerRadius: 12)) + + Button(action: { + locationManagerService.toggleMapMarkingMode(false) + locationManagerService.selectCoordinate() + }, label: { + Text("Cancel Map Location") + }) + + .padding(.all) + .background(Color.gray) + .clipShape(.rect(cornerRadius: 12)) + } + .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..26c83b7 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("Select based on Map") + } + }) + .buttonStyle(PlainButtonStyle()) + } + public var body: some View { VStack { headerView() @@ -223,16 +233,18 @@ public struct OriginDestinationSheetView: View { List { if search.isEmpty, isSearchFocused { + selectLocationBasedOnMap() currentUserSection() } else if search.isEmpty { favoritesSection() recentsSection() } else { + selectLocationBasedOnMap() searchResultsSection() } } .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..cde1f45 --- /dev/null +++ b/OTPKit/Services/LocationManagerService.swift @@ -0,0 +1,190 @@ +// +// 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() } } } From bb781efc0122bccb4ca363ef3c188e495f6d5357 Mon Sep 17 00:00:00 2001 From: hilmyveradin Date: Fri, 19 Jul 2024 00:12:39 +0700 Subject: [PATCH 2/4] add mark spacing --- OTPKit/Services/LocationManagerService.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/OTPKit/Services/LocationManagerService.swift b/OTPKit/Services/LocationManagerService.swift index cde1f45..1b57f2f 100644 --- a/OTPKit/Services/LocationManagerService.swift +++ b/OTPKit/Services/LocationManagerService.swift @@ -111,6 +111,7 @@ public final class LocationManagerService: NSObject, ObservableObject { } // MARK: - User Location Methods + public func checkIfLocationServicesIsEnabled() { DispatchQueue.global().async { if CLLocationManager.locationServicesEnabled() { From c4dac570337e9b867b9d23fb374bc9ccd272a4a1 Mon Sep 17 00:00:00 2001 From: Aaron Brethorst Date: Fri, 19 Jul 2024 14:12:34 -0700 Subject: [PATCH 3/4] Modify the MapMarkingView UI --- .../MapExtension/MapMarkingView.swift | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/OTPKit/Features/MapExtension/MapMarkingView.swift b/OTPKit/Features/MapExtension/MapMarkingView.swift index fb1cc0c..4364404 100644 --- a/OTPKit/Features/MapExtension/MapMarkingView.swift +++ b/OTPKit/Features/MapExtension/MapMarkingView.swift @@ -15,29 +15,34 @@ public struct MapMarkingView: View { VStack { Spacer() - VStack(spacing: 16) { - Button(action: { + 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("Add Map Location") - }) - .padding(.all) - .background(Color.gray) - .clipShape(.rect(cornerRadius: 12)) + } label: { + Text("Cancel") + .padding(8) + .frame(maxWidth: .infinity) + } + .buttonStyle(.bordered) - Button(action: { + Button { locationManagerService.toggleMapMarkingMode(false) locationManagerService.selectCoordinate() - }, label: { - Text("Cancel Map Location") - }) - - .padding(.all) - .background(Color.gray) - .clipShape(.rect(cornerRadius: 12)) + } label: { + Text("Add Pin") + .padding(8) + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) } - .padding(.bottom, 24) + .frame(maxWidth: .infinity) + .padding(16) } } } From 5ac946bca0a554a524c8167ba38ae62e5762861c Mon Sep 17 00:00:00 2001 From: hilmyveradin Date: Sat, 20 Jul 2024 11:06:42 +0700 Subject: [PATCH 4/4] add bottom padding so that it's not blocking the map marker and move the choose on map --- OTPKit/Features/MapExtension/MapMarkingView.swift | 1 + .../Sheets/OriginDestinationSheetView.swift | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OTPKit/Features/MapExtension/MapMarkingView.swift b/OTPKit/Features/MapExtension/MapMarkingView.swift index 4364404..bc5671a 100644 --- a/OTPKit/Features/MapExtension/MapMarkingView.swift +++ b/OTPKit/Features/MapExtension/MapMarkingView.swift @@ -44,6 +44,7 @@ public struct MapMarkingView: View { .frame(maxWidth: .infinity) .padding(16) } + .padding(.bottom, 24) } } diff --git a/OTPKit/Features/OriginDestination/Sheets/OriginDestinationSheetView.swift b/OTPKit/Features/OriginDestination/Sheets/OriginDestinationSheetView.swift index 26c83b7..0710845 100644 --- a/OTPKit/Features/OriginDestination/Sheets/OriginDestinationSheetView.swift +++ b/OTPKit/Features/OriginDestination/Sheets/OriginDestinationSheetView.swift @@ -217,7 +217,7 @@ public struct OriginDestinationSheetView: View { }, label: { HStack { Image(systemName: "mappin") - Text("Select based on Map") + Text("Choose on Map") } }) .buttonStyle(PlainButtonStyle()) @@ -233,13 +233,12 @@ public struct OriginDestinationSheetView: View { List { if search.isEmpty, isSearchFocused { - selectLocationBasedOnMap() currentUserSection() } else if search.isEmpty { + selectLocationBasedOnMap() favoritesSection() recentsSection() } else { - selectLocationBasedOnMap() searchResultsSection() } }