custom MKAnnotation not moving when coordinate set
If you're going to manually post those change events, you have to call willChangeValue(forKey:)
in willSet
, too. In Swift 4:
var coordinate: CLLocationCoordinate2D {
willSet {
willChangeValue(for: \.coordinate)
}
didSet {
didChangeValue(for: \.coordinate)
}
}
Or in Swift 3:
var coordinate: CLLocationCoordinate2D {
willSet {
willChangeValue(forKey: #keyPath(coordinate))
}
didSet {
didChangeValue(forKey: #keyPath(coordinate))
}
}
Or, much easier, just specify it as a dynamic
property and this notification stuff is done for you.
@objc dynamic var coordinate: CLLocationCoordinate2D
For Swift 2 version, see previous rendition of this answer.
Swift -How to Update Data in Custom MKAnnotation Callout?
There are a few issues:
You need to use the
@objc dynamic
qualifier for any properties you want to observe. The standard callout performs Key-Value Observation (KVO) ontitle
andsubtitle
. (And the annotation view observes changes tocoordinate
.)If you want to observe
userid
anddistance
, you have to make those@objc dynamic
as well. Note, you’ll have to makedistance
be non-optional to make that observable:var distance: CLLocationDistance
So:
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()
}
}Like I said, the standard callout observes
title
andsubtitle
. While you have to make the annotation properties observable, if you’re going to build your owndetailCalloutAccessoryView
, you’re going to have to do your own 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
}
}
That yields:
Use MKAnnotation to move on the next View Controller
Create a segue from your mapViewController
to your destination viewController
and then do it like this:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
self.performSegue(withIdentifier: "destViewController", sender: nil)
}
Here is a sample project I created that shows you how it works.
To edit the segue identifier do this:
Custom MKAnnotation Pin executing specific action when clicked -- custom pin not showing up
Interesting, I tested your code, altering the coordinates to be in New York (simulator location) and the button works fine for me. Are you sure your custom coordinates are where you want them? And why not use the user's location?
Try this:
func determineCurrentLocation() {
mapView.delegate = self
locationManager = CLLocationManager()
locationManager.delegate = self
mapView.showsUserLocation = true
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
@IBAction func tulipButton(_ sender: Any) {
dropPin()
}
func dropPin() { // Using the user's location
guard let location = locationManager.location else { return }
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
tulipPin = customPin(Title: "username", Subtitle: "caption", coordinate: center)
self.mapView.addAnnotation(tulipPin)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locationManager.location else { return }
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
mapView.setRegion(region, animated: true)
}
func mapView(_ mapview: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else { return nil }
let annotationView = MKAnnotationView(annotation: tulipPin, reuseIdentifier: "tulip")
annotationView.image = UIImage(named: "tulip")
annotationView.canShowCallout = true
annotationView.calloutOffset = CGPoint(x: -5, y: 5)
// let transform = CGAffineTransform(scaleX: 0.07, y: 0.07)
// annotationView.transform = transform
return annotationView
}
Related Topics
How to Perform Face Detection in Swift
Explit Conformance to Codable Removes Memberwise Initializer Generation on Structs
Argument of '#Selector' Does Not Refer to an '@Objc' Method, Property or Initializer
Playing Hls (M3U8) in Cocoa Os X Avplayer - Swift
Swift Protocol as Generic Parameter
Tvos Remote Notification Replacement
Calling Stop() on Avaudioplayernode After Finished Playing Causes Crash
Gcdasyncsocket Multiple Connections Wont Accept Data from Multiple Sockets
How to Implement a Generic Struct That Manages Key-Value Pairs for Userdefaults in Swift
Why Does This Swiftui Picker Code Not Work
How to Remove Node from Parent If Touched More Than Once
Iwatch: Wkinterfacelabel How to Stop Text from Being Cut Off with "..." at The End of a Label
Provide Simple Method to Get Current Speed (Implementing Speedometer)