Swift - 如何在自定义 MKAnnotation 标注中更新数据?
Swift -How to Update Data in Custom MKAnnotation Callout?
我的 mapView 有一个自定义注释。我最初用一些数据在上面设置了坐标、标题(例如"first title")、副标题(例如"first address")、userId 和距离(例如0 米)属性。我将它添加到 mapView 和数组以供以后使用。一切正常,它显示在 mapView 上,我按下它,标注显示初始数据。
我后来得知该标注的位置已更改。我遍历数组并使用坐标、标题(例如 "new title")、副标题(例如 "new address")和距离(例如 100 米)属性的新数据更新标注。我还将标注从它的原始位置动画化到它的新位置。动画效果很好,标注从 A 点移动到 B 点。
问题是当我点击注释时,标注上显示的是旧数据而不是新数据。
我用calloutAccessoryControlTapped
推一个新的vc。当我在那里放置一个断点时,自定义引脚具有所有新数据。错误似乎与标注有关。
我该如何解决这个问题?
我不想清除 mapView 中的所有注释,所以这不是一个选项。我调用 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 viewFor annotation
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
}
有几个问题:
您需要对要观察的任何属性使用 @objc dynamic
限定符。标准标注在 title
和 subtitle
上执行 Key-Value Observation (KVO)。 (注释视图观察到 coordinate
的变化。)
如果你想观察userid
和distance
,你也必须制作那些@objc dynamic
。请注意,您必须使 distance
成为 non-optional 才能使其可观察:
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()
}
}
就像我说的,标准标注观察 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
}
}
结果是:
我的 mapView 有一个自定义注释。我最初用一些数据在上面设置了坐标、标题(例如"first title")、副标题(例如"first address")、userId 和距离(例如0 米)属性。我将它添加到 mapView 和数组以供以后使用。一切正常,它显示在 mapView 上,我按下它,标注显示初始数据。
我后来得知该标注的位置已更改。我遍历数组并使用坐标、标题(例如 "new title")、副标题(例如 "new address")和距离(例如 100 米)属性的新数据更新标注。我还将标注从它的原始位置动画化到它的新位置。动画效果很好,标注从 A 点移动到 B 点。
问题是当我点击注释时,标注上显示的是旧数据而不是新数据。
我用calloutAccessoryControlTapped
推一个新的vc。当我在那里放置一个断点时,自定义引脚具有所有新数据。错误似乎与标注有关。
我该如何解决这个问题?
我不想清除 mapView 中的所有注释,所以这不是一个选项。我调用 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 viewFor annotation
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
}
有几个问题:
您需要对要观察的任何属性使用
@objc dynamic
限定符。标准标注在title
和subtitle
上执行 Key-Value Observation (KVO)。 (注释视图观察到coordinate
的变化。)如果你想观察
userid
和distance
,你也必须制作那些@objc dynamic
。请注意,您必须使distance
成为 non-optional 才能使其可观察: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() } }
就像我说的,标准标注观察
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 } }
结果是: