Reputation: 5829
I have the following code that fetches the gps coordinates from the user and puts a red marker in the place where he currently is:
class ViewController: UIViewController, CLLocationManagerDelegate {
@IBOutlet var mapView: MKMapView!
var locationManager: CLLocationManager?
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager!.delegate = self
if CLLocationManager.authorizationStatus() == .AuthorizedWhenInUse {
locationManager!.startUpdatingLocation()
} else {
locationManager!.requestWhenInUseAuthorization()
}
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
print("NotDetermined")
case .Restricted:
print("Restricted")
case .Denied:
print("Denied")
case .AuthorizedAlways:
print("AuthorizedAlways")
case .AuthorizedWhenInUse:
print("AuthorizedWhenInUse")
locationManager!.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.first!
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, 500, 500)
mapView.setRegion(coordinateRegion, animated: true)
locationManager?.stopUpdatingLocation()
let annotation = MKPointAnnotation()
annotation.coordinate = location.coordinate
longitude = location.coordinate.longitude
latitude = location.coordinate.latitude
mapView.addAnnotation(annotation)
locationManager = nil
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Failed to initialize GPS: ", error.description)
}
}
That works great and as you can see, I'm using these lines to get the user's lat and long to process it later on:
longitude = location.coordinate.longitude
latitude = location.coordinate.latitude
Now I want to add another functionality - user see his current location on the map as the red pin and can leave it as it is OR - the new feature - he can drag the red guy somewhere else and get its new longitude and latitude.
I've been looking here and there at the draggable feature and I've decided to add this code to my existing one:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is MKPointAnnotation {
let pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "myPin")
pinAnnotationView.pinColor = .Purple
pinAnnotationView.draggable = true
pinAnnotationView.canShowCallout = true
pinAnnotationView.animatesDrop = true
return pinAnnotationView
}
return nil
}
But it didn't do the trick and the pin is still not draggable. How can I fix that and fetch (of for now - print to the console) the new location of the pin each time user grabs it and moves it around?
Upvotes: 1
Views: 840
Reputation: 437372
If you're seeing a red marker rather than the purple marker you specified in your code sample, that would suggest that you haven’t registered your annotation view’s reuse identifier and/or the mapView(_:viewFor:)
isn't getting called.
Nowadays, to help avoid view controller bloat, we’d generally put the customization of the annotation view in its own subclass (in the spirit of the “single responsibility principle”):
class CustomAnnotationView: MKPinAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
pinTintColor = .purple
isDraggable = true
canShowCallout = true
animatesDrop = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then, in iOS 11 and later, you probably would remove the mapView(_:viewFor:)
entirely and instead just register that class and you’re done:
override func viewDidLoad() {
super.viewDidLoad()
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
}
In iOS versions prior to 11 (or if you have some complicated logic where you might have multiple types of annotations that you want to add) you’d want to specify the delegate
of the map view and implement mapView(_:viewFor:)
.
One can hook up the delegate in IB by control-dragging from the delegate
outlet in the “Connections Inspector” in IB, or by doing it programmatically, e.g. in viewDidLoad
:
mapView.delegate = self
I’d then add a constant to specify my preferred the reuse identifier for the custom annotation view:
class CustomAnnotationView: MKPinAnnotationView {
static let reuseIdentifier = Bundle.main.bundleIdentifier! + ".customAnnotationView"
// these two are unchanged from as shown above
override init(annotation: MKAnnotation?, reuseIdentifier: String?) { ... }
required init?(coder aDecoder: NSCoder) { ... }
}
And then, you’d implement mapView(_:viewFor:)
which will attempt to dequeue an previous annotation view and update its annotation
, if possible, or instantiate a new annotation view, if not:
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: CustomAnnotationView.reuseIdentifier) {
annotationView.annotation = annotation
return annotationView
}
return CustomAnnotationView(annotation: annotation, reuseIdentifier: CustomAnnotationView.reuseIdentifier)
}
}
See previous iteration of this answer for older Swift versions.
Upvotes: 3
Reputation: 2564
Tip: You can drag an annotation when it is selected, and it can be selected only when it shows a callout. It can show a callout, when it has a title. So unless you have a title, it won’t work.
Upvotes: 0