You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义MKMapView无法响应位置更新问题求助

解决SwiftUI自定义MKMapView不响应位置更新的问题

问题背景

在SwiftUI开发中,自定义MKMapView用于支持点击地图标注,但该视图无法响应位置管理器的位置更新,而原生Map()组件可正常随位置变化更新。

问题根源

  1. @State变量声明位置错误:在body计算属性内部声明@State变量region和coordinate,SwiftUI无法正确追踪这些变量的变化,导致更新无法传递到自定义视图。
  2. 自定义视图参数错误使用@State:CustomMKMapView中的coordinate参数被错误标记为@State,该参数应接收外部传入的更新值,而非内部维护状态。
  3. 重复添加标注:updateUIView方法中每次都调用addAnnotation,导致地图上不断叠加重复的自定义标注,且旧标注不会更新位置。
  4. 全局MKMapView实例:CustomMKMapView将mapView作为结构体属性,会导致视图重建时状态混乱,不符合UIViewRepresentable的生命周期规范。

修复步骤

  1. 调整@State变量位置:将region移到CustomMapView结构体顶层,通过监听locationMgr.location的变化来更新,或直接使用locationMgr.location.coordinate避免中间变量。
  2. 修正自定义视图参数:移除CustomMKMapView中coordinate的@State修饰符,改为普通只读属性。
  3. 优化标注更新逻辑:在updateUIView中先移除旧的自定义标注,再添加新标注,避免重复。
  4. 正确创建MKMapView实例:在makeUIView方法内部创建MKMapView,而非作为结构体属性。
  5. 同步地图区域更新:在updateUIView中根据新的coordinate更新地图的显示区域。

修复后的完整代码

import SwiftUI
import CoreLocation
import MapKit

struct CustomMapView: View {    
    @StateObject var locationMgr = LocManager()
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 51.500685, longitude: -0.124570),
        span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
    )

    var body: some View {
        VStack {    
            Map(coordinateRegion: $region)
                .frame(width: 400, height: 300)
            
            CustomMKMapView (
                coordinate: locationMgr.location.coordinate,
                title: "Me",
                setSelected: { annotation in
                    print(annotation ?? "No annotation selected")
                }
            )
            .frame(width: 400, height: 300)
        }
        .onReceive(locationMgr.$location) { newLocation in
            // 更新原生Map的区域
            region = MKCoordinateRegion(
                center: newLocation.coordinate,
                span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5)
            )
        }
    }
}

final class LocManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    @Published var location: CLLocation = CLLocation(latitude: 51.500685, longitude: -0.124570)
    @Published var direction: CLLocationDirection = .zero
    let locationManager = CLLocationManager()
    
    override init() {
        super.init()
        locationManager.delegate = self
        locationManager.distanceFilter = 100.0
        locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
        locationManager.startUpdatingLocation()
        
        Task { [weak self] in
            try? await self?.requestAuthorization()
        }
    }
    
    func requestAuthorization() async throws {
        if locationManager.authorizationStatus == .notDetermined {
            locationManager.requestWhenInUseAuthorization()
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {        
        if let location = locations.first {
            self.location = location

            // 测试用随机位置更新
            let randomInt = Int.random(in: 1..<2)
            if randomInt == 1 {
                self.location = CLLocation(latitude: 48.5307222, longitude: -0.1275)
            }
            
            print("new location \(location)")
        }
    }
}

struct CustomMKMapView: UIViewRepresentable {
    let coordinate: CLLocationCoordinate2D
    let title: String?
    let setSelected: ((MKAnnotation?) -> Void)?
    
    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView(frame: .zero)
        mapView.delegate = context.coordinator
        mapView.isUserInteractionEnabled = true
        mapView.selectableMapFeatures = [.pointsOfInterest]
        mapView.setRegion(.init(center: coordinate, span: .init(latitudeDelta: 0.02, longitudeDelta: 0.02)), animated: true)
        return mapView
    }
    
    func updateUIView(_ view: MKMapView, context: Context) {
        // 移除之前的自定义标注
        view.annotations
            .compactMap { $0 as? Annotation }
            .forEach(view.removeAnnotation)
        
        // 添加新的标注
        let newAnnotation = Annotation(title: title, coordinate: coordinate)
        view.addAnnotation(newAnnotation)
        
        // 更新地图区域
        view.setRegion(.init(center: coordinate, span: .init(latitudeDelta: 0.02, longitudeDelta: 0.02)), animated: true)
    }
    
    func makeCoordinator() -> MapViewCoordinator {
        MapViewCoordinator(self)
    }
    
    class MapViewCoordinator: NSObject, MKMapViewDelegate {
        let parent: CustomMKMapView
        
        init(_ parent: CustomMKMapView) {
            self.parent = parent
        }
        
        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            return nil
        }
        
        func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) {
            if let featureAnnotation = annotation as? MKMapFeatureAnnotation {
                parent.setSelected?(featureAnnotation)
            }
        }
    }
    
    class Annotation: NSObject, MKAnnotation {
        let title: String?
        let coordinate: CLLocationCoordinate2D
        
        init(title: String?, coordinate: CLLocationCoordinate2D) {
            self.title = title
            self.coordinate = coordinate
        }
    }
}

struct CustomMapView_Previews: PreviewProvider {
    static var previews: some View {
        CustomMapView()
    }
}

关键说明

  • 使用onReceive监听locationMgr.$location的变化,同步更新原生Map的区域。
  • 自定义MKMapView在updateUIView中先清理旧标注再添加新标注,避免重复。
  • 移除了所有错误的@State使用,确保数据流从位置管理器正确传递到视图。

内容的提问来源于stack exchange,提问作者rnaud

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 07:49:55