SwiftUI中UIViewRepresentable状态变更时MGLMapView实例异常求助
解决方案
核心问题
你在MapView结构体中把mapView声明为存储属性,而UIViewRepresentable是值类型,当父视图状态变化时,MapView实例会被重新创建,导致每次更新都会生成新的MGLMapView对象。但界面上显示的始终是makeUIView首次返回的实例,所以你后续操作的都是未挂载到界面的空地图,自然无法同步更新UI。
修复步骤
- 移除
MapView中的mapView存储属性:不要在结构体中持有地图实例,所有操作都基于updateUIView传入的uiView(这才是当前界面上的真实地图)。 - 优化
symbols传递:使用非可选的Binding,确保状态变化能正确传递到地图组件。 - 调整Coordinator逻辑:无需让Coordinator持有
MapView实例,直接在回调中使用传入的mapView参数。
修改后的完整代码
MapView结构体
struct MapView: UIViewRepresentable { @Binding var symbols: [MapMarkerSymbol] private let mapFiles: MapFiles // 根据你的实际类型调整 init(mapFiles: MapFiles, symbols: Binding<[MapMarkerSymbol]>) { self.mapFiles = mapFiles self._symbols = symbols } func updateUIView(_ uiView: MGLMapView, context: Context) { // 直接操作界面上的真实地图实例 uiView.removeAnnotations(uiView.annotations ?? []) // 添加新标记 for symbol in symbols { let annotation = MGLPointAnnotation() annotation.coordinate = CLLocationCoordinate2D(latitude: symbol.lat, longitude: symbol.lng) annotation.title = symbol.title annotation.subtitle = "" uiView.addAnnotation(annotation) } // 标记非空时缩放至边界 if !symbols.isEmpty { zoomToBounds(for: uiView) } } func makeUIView(context: Context) -> MGLMapView { let mapView = MGLMapView(frame: .zero) let styleURL = getStyleURL() mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] mapView.logoView.isHidden = true mapView.styleURL = styleURL mapView.delegate = context.coordinator return mapView } // 缩放逻辑改为接收当前地图实例 private func zoomToBounds(for mapView: MGLMapView) { guard !symbols.isEmpty else { return } let coordinates = symbols.map { CLLocationCoordinate2D(latitude: $0.lat, longitude: $0.lng) } let bounds = MGLCoordinateBounds(coordinates: coordinates) mapView.setVisibleCoordinateBounds(bounds, animated: true) } final class Coordinator: NSObject, MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool { return true } func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) { print("annotation tapped") } } func makeCoordinator() -> Coordinator { Coordinator() } // 实现你的样式URL获取逻辑 private func getStyleURL() -> URL { URL(string: "your-map-style-url")! } }
MapView使用代码
@StateObject private var viewModel = IOSFindBusinessesViewModel() var body: some View { ZStack{ if viewModel.state.isShowingMapLayout { let businessPins = viewModel.state.finalResultsToDisplay().map { business in MapMarkerSymbol(lat: business.lat, lng: business.lng, title: business.name) } // 直接传递常量Binding(如果不需要修改标记列表) MapView(mapFiles: viewModel.state.mapFiles, symbols: .constant(businessPins)) .frame(maxWidth: .infinity, maxHeight: .infinity) } } // 监听viewModel状态变化,确保视图更新 .onChange(of: viewModel.state) { _ in } }
关键说明
UIViewRepresentable的生命周期:makeUIView仅在首次加载时调用一次,后续所有更新都通过updateUIView传入已存在的视图实例,永远不要在结构体中存储视图对象。- 所有地图操作必须基于
updateUIView的uiView参数,或者Coordinator回调中传入的mapView参数,这两个才是界面上的真实地图实例。 - 不要在
body内部声明@State,@State应该放在视图结构体的顶层;直接使用viewModel.state即可,无需额外包装。
内容的提问来源于stack exchange,提问作者Charlie Walker
相关产品推荐
相关产品推荐

