如何更新 MKPinAnnotationView 的信息?

How to update information on MKPinAnnotationView?

我过去有过使用 MKMapViewMKPointAnnotation 的经验,我曾将它们放在地图上。 这次我想更进一步,用 MKPinAnnotationView 写一个标签和一些别针。

不幸的是,它并没有像我预期的那样工作。

这是我想要做的:

我有一张地图(一个 MKMapView 对象),当我触摸它时,我在触摸点放置了一个图钉,然后执行了一些计算,这给了我地图上的第二个点。我在地图上放了第二个图钉(位于第二个点),在最后一个图钉上我想放一个标签,比如 "Hello Second!",但是当图钉改变位置时需要更新这个标签。

相关代码如下:

class ViewController: UIViewController, MKMapViewDelegate {
    var mapView:MKMapView!, touchPoint,secondPoint:MKPointAnnotation!

    override func viewDidLoad() {
        super.viewDidLoad()
        mapView = MKMapView()
        ...........
        let mapTap = UITapGestureRecognizer(target: self,
                                            action: #selector(ViewController.mapTouchHandler))
        mapView.addGestureRecognizer(mapTap)
    }


    func mapTouchHandler(gesture:UITapGestureRecognizer) {
        ...........
        // Compute map coordinates for the touch point (tapGeoPoint).

        if touchPoint == nil {
            touchPoint = MKPointAnnotation()
            mapView.addAnnotation(touchPoint);
        }

        touchPoint.coordinate = CLLocationCoordinate2D(latitude: tapGeoPoint.latitude,
                                                       longitude: tapGeoPoint.longitude)
        ...........
        computeSecondPoint(url: someComputedURL)
    }


    func computeSecondPoint(url searchURL:String) {
        let reqURL = NSURL(string: searchURL)!, session = URLSession.shared,
        task = session.dataTask(with: reqURL as URL) {
            (data: Data?, response: URLResponse?, error: Error?) in
            if error == nil {
                do {let allData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSArray
                    .................
                    // Compute map coordinates for the second point (secondPointCoord).

                    if self.secondPoint == nil {
                        self.secondPoint = MKPointAnnotation()
                        self.mapView.addAnnotation(self.secondPoint)
                    }

                    DispatchQueue.main.async {
                        () -> Void in
                        self.secondPoint.coordinate = CLLocationCoordinate2D(latitude: secondPointCoord.latitude,
                                                                             longitude: secondPointCoord.longitude)
                        self.secondPoint.title = "Hello Second -TITLE!"
                        //* I want to update the label for this pin (attached to the secondPoint) too.
                    }
                } catch let error as NSError {print(error.localizedDescription)}
            } else {
                print("Error inside \(#function):\n\(error)")
            }
        }

        task.resume()

    }


    func mapView(_ mapView: MKMapView,
                 viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        let identifier = "pin"
        var view: MyPinAnnotationView
        if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
            as? MyPinAnnotationView {
            dequeuedView.annotation = annotation
            view = dequeuedView
        } else {
            view = MyPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)

            if ((annotation.coordinate.latitude != touchPoint.coordinate.latitude) ||
                (annotation.coordinate.longitude != touchPoint.coordinate.longitude)) {//* I need a better test to check that this not touchPoint!
                view.pinTintColor = UIColor.blue
                view.canShowCallout = true
                view.calloutOffset = CGPoint(x: -7, y: 0)
                view.setInfo(title: "Hi Start Label!")
            } else {
                view.pinTintColor = UIColor.red
                view.canShowCallout = false
            }
        }
        return view
    }
}

这是 class MyPinAnnotationView:

import UIKit
import MapKit

class MyPinAnnotationView: MKPinAnnotationView {
    let information:UILabel = UILabel(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 70.0, height: 30.0)))

    func setInfo(title : String)
    {
        information.text = title
        information.textAlignment = .center
        self.addSubview(information)
    }


    func hideInfo() {
        information.removeFromSuperview()
    }
}

注释标记为 //* 的行显示了我需要帮助的地方。

现在标签显示为第一组,但我不知道如何更新它。

如果您希望注释视图显示一些随着注释更改而更新的文本,请在注释上使用 KVO。所以,首先,创建一个模型对象,注解,其中包括要观察的新属性:

class MyAnnotation: NSObject, MKAnnotation {
    dynamic var title: String?
    dynamic var subtitle: String?
    dynamic var coordinate: CLLocationCoordinate2D
    dynamic var information: String?

    init(title: String? = nil, subtitle: String? = nil, coordinate: CLLocationCoordinate2D, information: String? = nil) {
        self.title = title
        self.subtitle = subtitle
        self.coordinate = coordinate
        self.information = information
    }
}

我将这个新名称命名为 属性 information,但您或许可以想出一个更好的名称来体现此 属性 的功能意图。但希望它能说明这个想法。这里的关键要点是,如果这个 属性 以后可能会发生变化,我们会想把它变成 dynamic 这样我们就可以使用 KVO 来观察这些变化。

class MyPinAnnotationView: MKPinAnnotationView {
    private let informationLabel = UILabel(frame: CGRect(origin: .zero, size: CGSize(width: 70.0, height: 30.0)))

    private var observerContext = 0

    override var annotation: MKAnnotation? {
        willSet {
            removeObserverIfAny()
        }
        didSet {
            if let annotation = annotation as? MyAnnotation {
                annotation.addObserver(self, forKeyPath: #keyPath(MyAnnotation.information), context: &observerContext)
                informationLabel.text = annotation.information
            }
        }
    }

    deinit {
        removeObserverIfAny()
    }

    private func removeObserverIfAny() {
        if let oldAnnotation = annotation as? MyAnnotation {
            oldAnnotation.removeObserver(self, forKeyPath: #keyPath(MyAnnotation.information))
        }
    }

    func showInformation() {
        addSubview(informationLabel)
    }

    func hideInformation() {
        informationLabel.removeFromSuperview()
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard context == &observerContext else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
            return
        }

        if let annotation = annotation as? MyAnnotation, let information = annotation.information {
            informationLabel.text = information
        }
    }

}

extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        if annotation is MKUserLocation { return nil }

        var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MyPinAnnotationView
        if annotationView == nil {
            annotationView = MyPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
            annotationView?.canShowCallout = true
        } else {
            annotationView?.annotation = annotation
        }
        annotationView?.showInformation()
        return annotationView
    }
}

我已经将标签的名称更改为 informationLabel 以使其更明确一点,它是一个视图,不要与模型混淆 属性, information,我们的新 MyAnnotation class。此外,我建议使用比 MyAnnotationMyPinAnnotationView 更有意义的 class 名称,或许使用一些更能体现这两个 class 的功能意图的名称。

无论如何,如您所见,当您为此注释视图设置 annotation 时,它会更新标签 text。但它也通过 KVO 观察注解的新 information 属性,所以如果这个 属性 稍后更改,视图将相应更新。