SwiftUI中如何将MKMapItem传递至Google Maps协调器?
问题:如何在SwiftUI中调用Google Maps协调器的addLocation()方法
我是SwiftUI新手,正在开发一款基于Google Maps的iOS应用,流程如下:用户在「搜索」视图中使用MapKit的搜索补全功能查找地点,选中地点后「信息」视图会展示详情,点击其中的保存按钮可在地图上添加标记。由于Google Maps不支持SwiftUI,部分代码使用Swift编写。
核心问题:如何实现保存按钮的动作,使其调用Google Maps协调器中的addLocation()辅助方法。
已尝试方案的问题
- 方案一:在
SearchLocationViewModel中添加@Published var locationToAdd: MKMapItem?,并在GoogleMapViewRepresentable中注入@EnvironmentObject var viewModel: SearchLocationViewModel,在updateUIView()中通过if viewModel.locationToAdd条件添加标记。但输入搜索内容或选中搜索补全结果时,SearchLocationViewModel的更新会触发updateUIView(),导致不必要的地图视图刷新。 - 方案二:在
addLocation()添加标记后立即设置viewModel.locationToAdd = nil,但会出现错误(nil无法初始化指定类型MKMapItem);即便没有错误,也会触发SearchLocationViewModel的再次更新及updateUIView()调用,逻辑不够严谨。
正确解决方案:拆分独立的地图标记视图模型
是的,单独创建一个仅负责地图标记操作的视图模型,与SearchLocationViewModel分离,是更符合单一职责原则、逻辑更清晰的方案,能避免搜索操作的更新影响地图视图。
具体实现步骤
- 创建独立的MapMarkerViewModel
import MapKit class MapMarkerViewModel: ObservableObject { @Published var locationToAdd: MKMapItem? }
- 修改GoogleMapViewRepresentable,注入MapMarkerViewModel
import SwiftUI import MapKit import GoogleMaps struct GoogleMapViewRepresentable: UIViewRepresentable { @EnvironmentObject var markerViewModel: MapMarkerViewModel let mapView = GMSMapView(frame: .zero) func makeCoordinator() -> MapCoordinator { return MapCoordinator(mapView: self) } func makeUIView(context: Context) -> GMSMapView { return mapView } func updateUIView(_ mapView: GMSMapView, context: Context) { guard let location = markerViewModel.locationToAdd else { return } // 调用协调器的添加方法 context.coordinator.addLocation(location) // 处理后重置,避免重复触发更新 markerViewModel.locationToAdd = nil } } extension GoogleMapViewRepresentable { class MapCoordinator: NSObject, CLLocationManagerDelegate { var parent: GoogleMapViewRepresentable init(mapView: GoogleMapViewRepresentable) { parent = mapView } func addLocation(_ item: MKMapItem) { // 实现添加Google Maps标记的具体逻辑 let coordinate = item.placemark.coordinate let marker = GMSMarker(position: coordinate) marker.title = item.name marker.snippet = item.placemark.title marker.map = parent.mapView print("添加地点标记: \(item.name ?? "未知地点")") } } }
- 在保存按钮动作中设置标记位置
在展示地点详情的「信息」视图中,注入MapMarkerViewModel,并在保存按钮点击时传递选中的地点:
struct InfoView: View { @EnvironmentObject var viewModel: SearchLocationViewModel @EnvironmentObject var markerViewModel: MapMarkerViewModel var body: some View { VStack { // 展示地点详情 Text(viewModel.selectedLocationResponseItem?.name ?? "无地点信息") Button("保存地点") { if let selectedItem = viewModel.selectedLocationResponseItem { markerViewModel.locationToAdd = selectedItem } } } } }
- 在应用入口注入两个视图模型
确保在父视图层级中同时注入两个ViewModel,让子视图可以通过EnvironmentObject访问:
@main struct YourMapApp: App { let searchViewModel = SearchLocationViewModel() let markerViewModel = MapMarkerViewModel() var body: some Scene { WindowGroup { ContentView() .environmentObject(searchViewModel) .environmentObject(markerViewModel) } } }
方案优势
- 职责分离:
SearchLocationViewModel专注处理搜索补全逻辑,MapMarkerViewModel仅负责地图标记的添加操作,代码结构更清晰。 - 避免无效刷新:只有当真正需要添加标记时,才会触发
GoogleMapViewRepresentable的updateUIView(),搜索操作的更新不会影响地图视图。 - 逻辑严谨:处理完标记添加后重置
locationToAdd为nil,不会导致重复触发,且可选类型赋值nil合法,不会出现之前的类型错误。
原问题相关代码
GoogleMapViewRepresentable(原始版本)
import SwiftUI import MapKit import GoogleMaps struct GoogleMapViewRepresentable: UIViewRepresentable { let mapView = GMSMapView(frame: .zero) func makeCoordinator() -> MapCoordinator { return MapCoordinator(mapView: self) } func makeUIView(context: Context) -> GMSMapView { return mapView } func updateUIView(_ mapView: GMSMapView, context: Context) { } } extension GoogleMapViewRepresentable { class MapCoordinator: NSObject, CLLocationManagerDelegate { var parent: GoogleMapViewRepresentable init(mapView: GoogleMapViewRepresentable) { parent = mapView } func addLocation(_ item: MKMapItem?) { print("Item: \(item)") } }}
SearchLocationViewModel(原始版本)
import MapKit class SearchLocationViewModel: NSObject, ObservableObject { @Published var results = [MKLocalSearchCompletion]() @Published var selectedLocationResponseItem: MKMapItem? private let searchCompleter = MKLocalSearchCompleter() var queryFragment: String = "" { didSet { searchCompleter.queryFragment = queryFragment } } override init() { super.init() searchCompleter.delegate = self searchCompleter.queryFragment = queryFragment searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address, .pointOfInterest]) } func searchLocation(forLocalSearchCompletion completion: MKLocalSearchCompletion, completionHandler: @escaping MKLocalSearch.CompletionHandler) { let searchRequest = MKLocalSearch.Request(completion: completion) let search = MKLocalSearch(request: searchRequest) search.start(completionHandler: completionHandler) } func selectLocation(_ localSearch: MKLocalSearchCompletion) { searchLocation(forLocalSearchCompletion: localSearch) { response, error in if let error = error { print("Error: \(error.localizedDescription)") return } guard let item = response?.mapItems.first else { return } self.selectedLocationResponseItem = item } } } extension SearchLocationViewModel: MKLocalSearchCompleterDelegate { func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { self.results = completer.results } }
内容的提问来源于stack exchange,提问作者user18810548
相关产品推荐
相关产品推荐

