diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/AddressMapPickerView.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/AddressMapPickerView.swift index 641136a9fe8..3807ff473c5 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/AddressMapPickerView.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/AddressMapPickerView.swift @@ -3,7 +3,6 @@ import SwiftUI import Observation import struct Yosemite.Country -@available(iOS 17, *) struct AddressMapPickerView: View { @Environment(\.dismiss) private var dismiss @State private var viewModel: AddressMapPickerViewModel @@ -18,12 +17,15 @@ struct AddressMapPickerView: View { var body: some View { NavigationStack { ZStack(alignment: .top) { - Map(coordinateRegion: $viewModel.region, - showsUserLocation: true, - annotationItems: viewModel.annotations) { item in - MapMarker(coordinate: item.coordinate) + Map(position: $viewModel.mapPosition) { + ForEach(viewModel.annotations) { item in + Marker(coordinate: item.coordinate) { + RoundedRectangle(cornerRadius: 5) + } + } } - .ignoresSafeArea() + .mapStyle(.standard(elevation: .realistic)) + .ignoresSafeArea() VStack(spacing: 0) { searchBar @@ -118,6 +120,12 @@ struct AddressMapPickerView: View { .autocorrectionDisabled() .textInputAutocapitalization(.never) .focused($isSearchFocused) + .onChange(of: isSearchFocused) { _, newValue in + viewModel.isSearchFocused = newValue + } + .onChange(of: viewModel.isSearchFocused) { _, newValue in + isSearchFocused = newValue + } if !viewModel.searchQuery.isEmpty { Button(action: { @@ -134,7 +142,6 @@ struct AddressMapPickerView: View { } } -@available(iOS 17, *) private extension AddressMapPickerView { enum Localization { static let close = NSLocalizedString("addressMapPicker.button.close", value: "Close", comment: "Text for the close button in the Edit Address Form.") diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/AddressMapPickerViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/AddressMapPickerViewModel.swift index 8b60b0cc243..1e91dbee6fd 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/AddressMapPickerViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/AddressMapPickerViewModel.swift @@ -25,7 +25,6 @@ final class DefaultAddressMapLocalSearchProvider: AddressMapLocalSearchProviding } } -@available(iOS 17, *) @Observable final class AddressMapPickerViewModel: NSObject { var searchQuery = "" { @@ -33,11 +32,13 @@ final class AddressMapPickerViewModel: NSObject { searchQueryContinuation.yield(newValue) } } + // Syncs with the focused state of the search text field in `AddressMapPickerView`. + var isSearchFocused: Bool = false private(set) var searchResults: [MKLocalSearchCompletion] = [] - var region = MKCoordinateRegion( + var mapPosition: MapCameraPosition = .region(MKCoordinateRegion( center: CLLocationCoordinate2D(latitude: 37.3361, longitude: -122.0380), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05) - ) + )) var annotations: [MapAnnotation] = [] var showsSearchResults: Bool { searchResults.isEmpty == false @@ -79,7 +80,12 @@ final class AddressMapPickerViewModel: NSObject { for await query in searchQueryStream.debounce(for: .seconds(0.3)) { if query.isEmpty { searchResults = [] - } else { + } else if isSearchFocused { + if let region = mapPosition.region { + // ~11km centered in the map region. + searchCompleter.region = .init(center: .init(latitude: region.center.latitude, longitude: region.center.longitude), + span: .init(latitudeDelta: 0.1, longitudeDelta: 0.1)) + } searchCompleter.queryFragment = query } } @@ -124,7 +130,6 @@ final class AddressMapPickerViewModel: NSObject { } } -@available(iOS 17, *) private extension AddressMapPickerViewModel { func configureSearchCompleter() { searchCompleter.resultTypes = .address @@ -140,10 +145,10 @@ private extension AddressMapPickerViewModel { let currentLocation = locationManager.location { DispatchQueue.main.async { [weak self] in guard let self else { return } - region = MKCoordinateRegion( + mapPosition = .region(MKCoordinateRegion( center: currentLocation.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) - ) + )) } return } @@ -159,10 +164,10 @@ private extension AddressMapPickerViewModel { return } - region = MKCoordinateRegion( + mapPosition = .region(MKCoordinateRegion( center: location.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) - ) + )) annotations = [MapAnnotation(coordinate: location.coordinate)] } catch { DDLogError("⛔️ Error geocoding initial address: \(error)") @@ -173,20 +178,13 @@ private extension AddressMapPickerViewModel { @MainActor func onSelectedPlacemark(_ placemark: MKPlacemark) async { - await withCheckedContinuation { continuation in - withAnimation { [weak self] in - guard let self else { return } - searchResults = [] - region = MKCoordinateRegion( - center: placemark.coordinate, - span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) - ) - annotations = [MapAnnotation(coordinate: placemark.coordinate)] - selectedPlace = placemark - } completion: { - continuation.resume() - } - } + searchResults = [] + mapPosition = .region(MKCoordinateRegion( + center: placemark.coordinate, + span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) + )) + annotations = [MapAnnotation(coordinate: placemark.coordinate)] + selectedPlace = placemark } func formatAddressString(fields: AddressFormFields) -> String { @@ -196,7 +194,6 @@ private extension AddressMapPickerViewModel { } } -@available(iOS 17, *) extension AddressMapPickerViewModel: MKLocalSearchCompleterDelegate { func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { searchResults = completer.results diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/Addresses/AddressMapPickerViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/Addresses/AddressMapPickerViewModelTests.swift index 64d1e7e9b8b..50748850a07 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/Addresses/AddressMapPickerViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Orders/Order Details/Addresses/AddressMapPickerViewModelTests.swift @@ -22,7 +22,6 @@ struct AddressMapPickerViewModelTests { // MARK: - Initialization Tests - @available(iOS 17, *) @Test func initialization_with_empty_fields_sets_properties_with_default_values() { // Given let emptyFields = AddressFormFields() @@ -38,7 +37,6 @@ struct AddressMapPickerViewModelTests { // MARK: - Selection Tests - @available(iOS 17, *) @Test func selectLocation_updates_annotations_and_hasValidSelection() async { // Given let fields = AddressFormFields() @@ -56,7 +54,6 @@ struct AddressMapPickerViewModelTests { // MARK: - Address Field Updates Tests - @available(iOS 17, *) @Test func updateFields_with_no_selected_place_does_not_modify_fields() { // Given let sut = AddressMapPickerViewModel(fields: .init(), countryByCode: mockCountryByCode) @@ -72,7 +69,6 @@ struct AddressMapPickerViewModelTests { #expect(updatedFields.city == "Original City") } - @available(iOS 17, *) @Test func updateFields_when_country_not_found_in_countryByCode_sets_country_and_state_as_strings() async { // Given let mockSearchProvider = MockAddressMapLocalSearchProvider.withFrenchAddress() @@ -95,7 +91,6 @@ struct AddressMapPickerViewModelTests { #expect(updatedFields.selectedState == nil) } - @available(iOS 17, *) @Test func updateFields_when_country_is_found_in_countryByCode_sets_selected_country_and_state() async { // Given let mockSearchProvider = MockAddressMapLocalSearchProvider.withUSAddress()