How to link an annotation in MapView to its calling pin - ios

Swift 4 iOS 11.x
Learning about mapViews and annotations and I can quite get my mind around some functionality I would like to do.
I create a mapView, and I add an annotation to it with a button, so far so good. I want the button to be a delete pin one, so it looks like this.
Now when I click on the blue no entry I want it to delete the black pin that it is connected too. But how to trace the link of the annotation to its pin. I get a call back with the button and thru the accessory view. I can lookup the title of the view and find the link, but surely there is a better method.

You need to use this method func mapView(_ mapView: MKMapView,annotationView view: MKAnnotationView,calloutAccessoryControlTapped control: UIControl) of MKMapViewDelegate
Something like this
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView,
calloutAccessoryControlTapped control: UIControl) {
//Here you have the annotation that was selected
let selectedAnnotation = view.annotation
//Do whatever you need here
}

Related

Restrict MKAnnotationView to drag only over/with already drawn polyline

I have an array of coordinates and draw a polyline using these. At center of polyline I m showing an annotation using MKAnnotationView. This is dragable, I want it to be dragged only with adjesent to or over the polyline. Do not allow user to move and drag away from polyline?
How this could be done any ideas ?
I only found this delegate method
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
}
but it is calling after position change and its view.annotaion.coordinates are read only. I can not reset them...
Please let me know if this is possible using MKMapView? Any possible way to achieve?

Mapbox: Refreshing an annotationView when the annotation changes

I have custom annotations that sometimes display a textView above them.
They don't display a textView if a variable named text on my annotation is nil.
An annotation may have text to display, but the value of the text variable could change while the annotation is being displayed. In this case I would like the annotation to refresh so that it is no longer displaying the textView.
I already have a delegate function that either creates an annotation with a textView if the annotations text variable is set and creates an annotation without a textView if the text variable of the annotation is not set, it works something like this, although this is not the actual code
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView?{
if annotation is MyCustomAnnotation{
if annotation.hasText(){
return MyCustomAnnotationView(hasText: True)
}else{
return ViewWithoutTextView(hasText: False)
}
}
But if the annotation changes from having text to not having text or vice versa while the annotation is already being displayed, then I don't know how to refresh this or call this again so that the right annotation view is displayed
As #Magnas said in the comment, you would have to remove the annotation and re-add it to update the state.
It would be better to create one custom annotation view that has the logic to handle hiding/showing of the text view inside it. Then you just hold onto a reference of the annotation and update that through the annotationView without going through and messing with map annotations at all.
A rough example (lots of blanks to fill):
// your methods in your custom annotation. Use these wherever you want to change things
class CustomAnnotation: MGLAnnotationView {
func showText() { }
func hideText() { }
}
// Define data structure to access your annotation with some kind of key
dataSourceToAnnotationView: [String: CustomAnnotation]
// save your annotations so you can access them later
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "customReuseId")
if annotationView == nil {
annotationView = CustomAnnotation()
let key = "exampleKeyString"
dataSourceToAnnotationView[key] = annotationView as! CustomAnnotation
}
return annotationView
}

How can I change mapbox annotation view?

I've add several annotations with custom annotation view, but I have a reference only to the marker and not to the annotation view itself. So how can I change the annotation view elements?
You should store the annotations in a variable and when you want to change their views just remove them from the map with mapView.removeAnnotation(...) and add it back again with mapView.addAnnotation(...).
The method
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
}
will be called and you should return your changed view.
Pay attention to the fact that for performance reasons you should not use addAnnotation() or removeAnnotation() if you have a lot of annotations, but instead addAnnotations() and removeAnnotations()
I added all my annotation views to a dictionary
I created a global dictionary variable
var annotationViews = [CustomAnnotation:CustomAnnotationView]()
Then after creating each annotation view within my delegate function
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView?
I added it into my dictionary
let annotationView = MyAnnotationView(reuseIdentifier: rid, size: CGSize(width: 45, height: 45), annotation: annotation)
annotationViews[annotation as! MyAnnotation] = annotationView
Later when I wanted to change the text of a text view within my annotation view, I used my annotation in the dictionary call. This returned the annotation view that corresponded to it.
self.annotationViews[anno]?.textView.text = "My new text view status"
No deleting and re-adding required.
You could probably insert something smaller as the key for the dictionary, like annotation.uid or annotation.name or something. I just used the whole annotation object though

Can't select MKViewAnnotation twice?

I've got pins placed on a map and when I tap on them I'm calling the didSelect. The function only gets called the first time the pin is tapped, and after that it's not called on that same pin again unless I select another pin and then come back and tap it.
What that sounds like to me is the pin is being selected, and didSelect can only be called in unselected pins, so when I go tap on another pin it's deselecting the first pin and making it tappable again.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
view.isSelected = false
}
I don't understand why the above code does not work.
How can I allow my annotations be tapped more than one time in a row?
Try with this method deselectAnnotation
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
//do what you need here
mapView.deselectAnnotation(view.annotation, animated: true)
}
Hope this helps
There is another option, and that is to add a Gesture Recognizer for on the annotationView.
This will enable showing the callout view (since de-selecting the annotation immediately will not show it).
internal func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(mapPinTapGestureRecognizer)))
}
#objc private func mapPinTapGestureRecognizer(gestureRecognizer: UITapGestureRecognizer) {
// Will get called on the second time the pin is selected.
// And then, after that, it will be called every time.
}
Just don't forget to remove the recognizer when the annotation is no longer selected.
internal func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
// Remove the gesture recognizer when the annotation is no longer selected.
}

Creating a dynamic subview in swift iOS Map app

This is my first foray into the app world so it's taken a lot of research to get to this point. I'm building a map application and am going for an interface similar to the zillow app seen below. I am trying to come up with the right approach that allows me to click on a map annotation and it brings up a smaller view where I can interact with it. So essentially I have a few questions:
Should I use a subview inside the map controller, or use a container view. Or is there another approach I haven't seen?
How do I push data from the annotation to that function?
How do I keep this subview hidden until an annotation has been clicked?
So far this is the closest thing I can find: Customize MKAnnotation Callout View?
Thanks!
I am new to iOS also, but I have done something similar to what you want to do. I have a view that shows some statistics, speed, bearing, etc. When someone clicks on the annotation, I toggle showing and hiding the statistics. There may be better ways, but here is what I did.
"Executive summary for your questions"
1 and 3) Use a subview over the map that you hide and unhide
2) Subclass both MKAnnotation and MKAnnotationView. Put the data you want to pass in a property of the subclassed MKAnnotationView, and then transfer the property to the MKAnnotationView when you create it. You can then retrieve it from the view passed in to didSelectAnnotationView.
Details
1) and 3) I created a subview that sits on the mapView and set it as hidden in the story board initially. I then have a toggeleMarkerStatistics() func
tion that toggles the visibility of the view. So something like this
func toggleMarkerStatistics() {
if mapMarkerStatistics.hidden {
mapMarkerStatistics.hidden = false
} else {
mapMarkerStatistics.hidden = true
}
}
This function is called from within
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) { }
2) To get data into the didSelectAnnotationView, here is what I did.
I subclassed both MKAnnotation and MKAnnotationView and added properties to hold the data that I wanted to pass to didSelectAnnotationView. So something like this:
class MyAnnotation: MKPointAnnotation {
var myMarker: MyMapMarker?
}
class MyMKAnnotationView: MKAnnotationView {
var myMarker: MyMapMarker?
}
When you create the annotation, set the property, before you add the annotation to the map.
let annotation = MyAnnotation()
annotation.myMarker = marker
annotation.coordinate = location
annotation.title = "btw, you MUST have a title or bad things happen"
mapView.addAnnotation(annotation)
Then in viewForAnnotation, you will be given your custom annotation with the property you set after you created it and you are asked to create a view for this annotation. Now when you create this view, set the view property to annotation property before you return the view. Now the data you want to pass to didSelectAnnotationView will be available on the view passed to didSelectAnnotationView
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let view: MKAnnotationView! = mapView.dequeueReusableAnnotationViewWithIdentifier("marker") ?? MyMKAnnotationView(annotation: annotation, reuseIdentifier: "marker")
view.annotation = annotation
view.canShowCallout = false // you need this to make this work
if let annotation = annotation as? MyAnnotation {
if let view = view as? MyMKAnnotationView {
view.myMarker = annotation.myMarker
}
}
return view
}
Now in didSelectAnnotationView, retrieve the data you set when you created the annotation:
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
if let view = view as? MyMKAnnotationView {
if let marker = view.myMarker {
toggleMarkerStatistics() // hide or unhide the view
// do something with your data
}
}
}
Note:
I tried to copy and simplify this from my code, which actually tries to support both Apple and Google maps, so hopefully I don't have any typo's, but I think is a good representation of the steps I take.
A few more things to note:
I think you must provide a title for the annotaion
I think you must set the view's canShowCallout to false
I think both of these requirements can be found in the documentation, but I don't have a pointer to this right now.

Resources