Swift - 如何更新自定义 MKAnnotation 标注中的数据?

问题描述 投票:0回答:2

我的地图视图有一个自定义注释。我最初使用一些数据设置了坐标、标题(例如“第一个标题”)、副标题(例如“第一个地址”)、userId 和距离(例如 0 米)属性。我将其添加到 mapView 和数组中以供以后使用。一切正常,它显示在地图视图上,我按下它,标注显示初始数据。

后来我得知该标注的位置已更改。我循环遍历数组并使用坐标、标题(例如“新标题”)、副标题(例如“新地址”)和距离(例如 100 米)属性的新数据更新标注。我还将标注从原始位置动画化到新位置。动画效果很好,标注从 A 点移动到 B 点。

问题是当我点击注释时,旧数据会显示在标注上,而不是新数据。

我使用

calloutAccessoryControlTapped
来推送新的 vc。当我在那里放置断点时,自定义引脚具有所有新数据。该错误似乎是在标注时发生的。

我该如何解决这个问题?

我不想清除地图视图中的所有注释,所以这不是一个选项。我调用

mapView.removeAnnotation(customPin)
mapView.addAnnotation(customPin)
解决了该图钉的问题,但是当图钉被移除并添加回地图时,会出现闪烁,然后当它动画到新位置时,它看起来不稳定。

自定义注释

class CustomPin: NSObject, MKAnnotation {

    @objc dynamic var coordinate: CLLocationCoordinate2D
    var title: String?
    var subtitle: String?
    var userId: String?
    var distance: CLLocationDistance?

    init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String, userId: String, distance: CLLocationDistance?) {

        self.coordinate = coordinate
        self.title = title
        self.subtitle = subtitle
        self.userId = userId
        self.distance = distance

        super.init()
    }
}

第一次使用初始数据设置注释

firstFunctionThatGetsTheInitialLocation(origLat, origLon) {

   let firstCoordinate = CLLocationCoordinate2DMake(origLat, origLon)   

   let distanceInMeters: CLLocationDistance = self.center.distance(from: anotherUsersLocation)

   let customPin = CustomPin(coordinate: firstCoordinate, title: "first title", subtitle: "first address", userId: "12345", distance: distance)

    DispatchQueue.main.async { [weak self] in

      self?.mapView.addAnnotation(customPin)

      self?.arrOfPins.append(customPin)
    }
}

第二次使用新数据设置注释

secondFunctionThatGetsTheNewLocation(newCoordinate: CLLocationCoordinate2D, newDistance: CLLocationDistance) {

    for pin in customPins {

        pin.title = "second title" // ** updates but the callout doesn't reflect it
        pin.subTitle = "second address" // ** updates but the callout doesn't reflect it
        pin.distance = newDistance // ** updates but the callout doesn't reflect it

       // calling these gives me the new data but the annotation blinks and moves really fast to it's new location
       // mapView.removeAnnotation(pin)
       // mapView.addAnnotation(pin)

        UIView.animate(withDuration: 1) {
            pin.coordinate = newCoordinate // this updates and animates to the new location with no problem
        }
    }
}

MapView视图用于注释

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

    if annotation.isKind(of: MKUserLocation.self) { return nil }

    guard let annotation = annotation as? CustomPin else { return nil }

    let reuseIdentifier = "CustomPin"

    var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)

    if annotationView == nil {
        annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
        annotationView?.canShowCallout = true
        annotationView?.calloutOffset = CGPoint(x: -5, y: 5)

        annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)

        annotationView?.image = UIImage(named: "chevronImage")

    } else {
        annotationView?.annotation = annotation
    }

    annotationView?.detailCalloutAccessoryView = nil
    annotationView?.detailCalloutAccessoryView = createCallOutWithDataFrom(customPin: annotation)

    return annotationView
}

创建用于标注的 UIView

func createCallOutWithDataFrom(customPin: CustomPin) -> UIView {

    let titleText = customPin.title
    let subTitleText = customPin.subTitle
    let distanceText = subTitle.distance // gets converted to a string

    // 1. create a UIView
    // 2. create some labels and add the text from the title, subTitle, and distance and add them as subViews to the UIView
    // 3. return the UIView
}
ios swift mkannotation calloutview
2个回答
6
投票

有几个问题:

  1. 您需要对要观察的任何属性使用

    @objc dynamic
    限定符。标准标注对 title
    subtitle
    执行
    键值观察
    (KVO)。 (注释视图观察到
    coordinate
    的变化。)

  2. 如果你想观察

    userid
    distance
    ,你也必须制作那些
    @objc dynamic
    。请注意,您必须使
    distance
    成为非可选才能使其可观察:

    var distance: CLLocationDistance
    

    所以:

    class CustomAnnotation: NSObject, MKAnnotation {
        // standard MKAnnotation properties
    
        @objc dynamic var coordinate: CLLocationCoordinate2D
        @objc dynamic var title: String?
        @objc dynamic var subtitle: String?
    
        // additional custom properties
    
        @objc dynamic var userId: String
        @objc dynamic var distance: CLLocationDistance
    
        init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String, userId: String, distance: CLLocationDistance) {
            self.userId = userId
            self.distance = distance
            self.coordinate = coordinate
            self.title = title
            self.subtitle = subtitle
    
            super.init()
        }
    }
    
  3. 就像我说的,标准标注遵守

    title
    subtitle
    。虽然您必须使注释属性可观察,但如果您要构建自己的
    detailCalloutAccessoryView
    ,则必须执行自己的 KVO:

    class CustomAnnotationView: MKMarkerAnnotationView {
        private let customClusteringIdentifier = "..."
    
        override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
            canShowCallout = true
            detailCalloutAccessoryView = createCallOutWithDataFrom(customAnnotation: annotation as? CustomAnnotation)
            clusteringIdentifier = customClusteringIdentifier
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        deinit {
            removeAnyObservers()
        }
    
        override var annotation: MKAnnotation? {
            didSet {
                removeAnyObservers()
                clusteringIdentifier = customClusteringIdentifier
                if let customAnnotation = annotation as? CustomAnnotation {
                    updateAndAddObservers(for: customAnnotation)
                }
            }
        }
    
        private var subtitleObserver: NSKeyValueObservation?
        private var userObserver: NSKeyValueObservation?
        private var distanceObserver: NSKeyValueObservation?
    
        private let subtitleLabel: UILabel = {
            let label = UILabel()
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }()
    
        private let userLabel: UILabel = {
            let label = UILabel()
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }()
    
        private let distanceLabel: UILabel = {
            let label = UILabel()
            label.translatesAutoresizingMaskIntoConstraints = false
            return label
        }()
    }
    
    private extension CustomAnnotationView {
        func updateAndAddObservers(for customAnnotation: CustomAnnotation) {
            subtitleLabel.text = customAnnotation.subtitle
            subtitleObserver = customAnnotation.observe(\.subtitle) { [weak self] customAnnotation, _ in
                self?.subtitleLabel.text = customAnnotation.subtitle
            }
    
            userLabel.text = customAnnotation.userId
            userObserver = customAnnotation.observe(\.userId) { [weak self] customAnnotation, _ in
                self?.userLabel.text = customAnnotation.userId
            }
    
            distanceLabel.text = "\(customAnnotation.distance) meters"
            distanceObserver = customAnnotation.observe(\.distance) { [weak self] customAnnotation, _ in
                self?.distanceLabel.text = "\(customAnnotation.distance) meters"
            }
        }
    
        func removeAnyObservers() {
            subtitleObserver = nil
            userObserver = nil
            distanceObserver = nil
        }
    
        func createCallOutWithDataFrom(customAnnotation: CustomAnnotation?) -> UIView {
            let view = UIView()
            view.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(subtitleLabel)
            view.addSubview(userLabel)
            view.addSubview(distanceLabel)
    
            NSLayoutConstraint.activate([
                subtitleLabel.topAnchor.constraint(equalTo: view.topAnchor),
                subtitleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                subtitleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                subtitleLabel.bottomAnchor.constraint(equalTo: userLabel.topAnchor),
    
                userLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                userLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                userLabel.bottomAnchor.constraint(equalTo: distanceLabel.topAnchor),
    
                distanceLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                distanceLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                distanceLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            ])
    
            if let customAnnotation = customAnnotation {
                updateAndAddObservers(for: customAnnotation)
            }
    
            return view
        }
    }
    

结果: animated callout changes


0
投票

针对更简单情况的快速技巧...

在大多数情况下,这是让您早点回家的快速技巧。

您的注释有许多自定义字段

class CatAnnotation: MKPointAnnotation {
    var popularityColor: UIColor = .purple // default
    .. many more
}

您希望注释视图在您更改地图模式时做出响应。

var fancyMapMode = false

@IBAction func tapChangeMapMode() {
    let ourAnnotations = mapView.annotations.compactMap{$0 as? CatAnnotation}
    if fancyMapMode {
        for i in 0..<ourAnnotations.count {
            // plain defaults ...
            ourAnnotations[i].popularityColor = .purple
            .. many more, back to defaults
            ourAnnotations[i].title = default title
        }
    }
    else {
        for i in 0..<ourAnnotations.count {
            // fancy special values ...
            ourAnnotations[i].popularityColor = .. % color
            .. many more
            ourAnnotations[i].title = default title + "\n#\(i) popular!!"
        }
    }
    fancyMapMode = !fancyMapMode
}

所以你正在改变所有特殊的颜色、视图等。

通常标题也会改变。 荣耀归来,标题已经可以 KVO 了。

只需将其添加到您的自定义注释视图中:

class CatAnnotationView: MKMarkerAnnotationView {
    
    private var titleObs: NSKeyValueObservation?
    override var annotation: (any MKAnnotation)? {
        didSet {
            if let ca = annotation as? CatAnnotation {
                titleObs = ca.observe(\.title){ [weak self] _,_ in
                    self?.yourCustomImage.tintColor = ca.popularity
                    .. make as many changes as you want,
                    .. simply reading ca.yourValues
                }
            }
        }
    }

缺点:在代码中为标记设置所有自定义值,您只需必须设置.title =更改last

缺点:如果你不改变标题,那就太糟糕了。一个简单的技巧就是在末尾添加/删除换行符(这在地图视图上确实是完全无害的)。

© www.soinside.com 2019 - 2024. All rights reserved.