I am searching an answer for adding gesture to the calloutbubble of annotation pin.
I tried different solutions, but they did not work for me.
Here is the latest one:
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView{
let rightButton = UIButton(type: UIButtonType.detailDisclosure)
let gesture = UITapGestureRecognizer(target: self, action: #selector(callout(gesture:)))
rightButton.addGestureRecognizer(gesture)
view.rightCalloutAccessoryView = rightButton
}
#objc func callout(gesture: UITapGestureRecognizer){
print("tapped")
}
Correct delegate method
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
///
}
plus
self.mapView.delegate = self
Related
I have a view controller which gets an adapter object to be able to use MapKit.
On that adapter I implement 2 gesture (long press + tap) as always but only long press does not work.
How I prepare gestures:
private func prepareGestures(on pinView: PinViewProtocol) {
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(sender:)))
longPressGesture.delegate = self
longPressGesture.minimumPressDuration = Constants.longPressDuration
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
tapGesture.delegate = self
pinView.addGestureRecognizers([tapGesture, longPressGesture])
}
How I add:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let annotation = annotation as? MyPinAnnotation else {
return nil
}
prepareGestures(on: pinView)
return pinView
}
How I add that object in my view controller:
private func configureMapView() {
mapViewAdapter = TestMapViewAdapter(mapView: mapView)
}
I have a MKMapView that has annotations. My goal is to hide the annotation, if one is selected, when the map finished scrolling.
When an annotation is called I assign the annotation into a variable to keep track of it.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
lastSelectedAnnotation = view.annotation
}
I know of:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool){ }
However, I cannot figure out (beginner here), how to detect that the map finished changing its region so I can call my function:
func hideSelectedAnnotation(_ mapView: MKMapView) {
DispatchQueue.main.async {
mapView.deselectAnnotation(self.lastSelectedAnnotation, animated: true)
self.lastSelectedAnnotation = nil
}
}
I hide the annotation also when an accessory button is tapped:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl){
hideSelectedAnnotation(mapView)}
I have tried saving the coordinate of the region, and comparing them to the map but the map does not neccessarily center the annotation. I could also start a timer and when regionDidChangeAnimated is no longer called hide the annotation. But that seams like butchering it.
Thanks for any help!
I think you already figured it out...
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool){
// Call your function here
}
Should fire every time the map view region changes (unless the change was inflicted by the user's finger)
----- EDIT -----
Unfortunately, you have to detect user input using a UIPanGestureRecognizer.
I have used, with success, A UIPanGestureRecognizer like the following:
lazy var mapPanGestureRecognizer: UIPanGestureRecognizer = {
let gr = UIPanGestureRecognizer(target: self, action: #selector(draggedMap))
gr.delegate = self
return gr
}()
You will also have to add the UIPanGestureRecognizer to the map with
yourMap.addGestureRecognizer(mapPanGestureRecognizer)
You can then manage what happens in the #selector function by checking the state of the gesture, like so
#objc func draggedMap(panGestureRecognizer: UIPanGestureRecognizer) {
// Check to see the state of the passed panGestureRocognizer
if panGestureRecognizer.state == UIGestureRecognizer.State.began {
// Do something
}
}
The state is what allows you to determine if the user ended a gesture, is in the middle of a gesture, or began a gesture.
List of possible states
I'm trying to change pin color in didSelect view: MKAnnotationView:
func mapView(_ mapView: MKMapView,
didSelect view: MKAnnotationView) {
let selectedAnnotation = view.annotation as? MKPointAnnotation //ColorPointAnnotation
//self.textField.text = selectedAnnotation!.title
preferredSpot = selectedAnnotation!.title!
view.tintColor = UIColor.green
}
But it's not changed when I tap on a pin - remains red. Does anybody know why and/or how to change it?
Use pinTintColor instead of tintColor
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let view = view as? MKPinAnnotationView {
view.pinTintColor = UIColor.green
}
}
The delegate function is:
optional func mapView(_ mapView: MKMapView,
didSelect view: MKAnnotationView)
Since pin tint is now deprecated, I thought I'd share my updated version based on Kosuke's answer with markerTint.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let view = view as? MKMarkerAnnotationView {
view.markerTintColor = UIColor.systemBlue
}
}
My calloutAccessoryControlTapped is also called when I just tap on annotation view and this behavior it's right. But how can I detect if the user has tapped on the right accessory view (in my case a detail disclosure button) and not just in the view?
I added a simple check but it doesn't work.
import UIKit
import MapKit
extension MapVC: MKMapViewDelegate, CLLocationManagerDelegate
{
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl)
{
if control == view.rightCalloutAccessoryView
{
... // enter here even if I tapped on the view annotation and not on button
}
}
}
To achieve it you would need to add target for the right accessory view. You can achieve it by setting button to rightCalloutAccessoryView as shown in the code snippet.
class MapViewController: UIViewController, MKMapViewDelegate {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is Annotation {
let annotationView = AnnotationView(annotation: annotation, reuseIdentifier: "reuseIdentifier")
let rightButton = UIButton(type: .DetailDisclosure)
rightButton.addTarget(self, action: #selector(didClickDetailDisclosure(_:)), forControlEvents: .TouchUpInside)
annotationView.rightCalloutAccessoryView = rightButton
}
return nil
}
func didClickDetailDisclosure(button: UIButton) {
// TODO: Perform action when was clicked on right callout accessory view.
}
}
// Helper classes.
class Annotation: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
}
}
class AnnotationView: MKAnnotationView {
}
Using UIView and UITapGestureRecognizer instead of UIControl
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "reuseIdentifier")
let gestureView = UIView(frame:CGRect(x: 0,y: 0,width: 20,height: 20))
let gestureRecognizer = UITapGestureRecognizer()
gestureRecognizer.addTarget(self, action: #selector(MapViewController.didClickGestureRecognizer(_:)))
gestureView.addGestureRecognizer(gestureRecognizer)
gestureView.backgroundColor = UIColor.redColor()
annotationView.rightCalloutAccessoryView = gestureView
return annotationView
}
func didClickGestureRecognizer(sender:UITapGestureRecognizer) -> Void {
print("didClickGestureRecognizer")
}
When you click on rightCalloutAccessoryView, only didClickGestureRecognizer will be called,but your calloutAccessoryControlTapped cannot be invoked anyone.
2.If you have a UIControl as rightCalloutAccessoryView,you can tap directly on MKAnnotationView.Otherwise MKAnnotationView cannot be tapped.
Both your selector and calloutAccessoryControlTapped will be called when you tap rightCalloutAccessoryView or tap directly on MKAnnotationView
3.If you have a UIControl as leftCalloutAccessoryView,both your selector and calloutAccessoryControlTapped will be called when you tapped on it.
4.Since iOS 9,you can has a detailCalloutAccessoryView in your MKAnnotationView.Only your selector will be called when you tapped on it.
5.Also you can create your own custom MKAnnotationView,and change its behaviors.
Im working with MKMapView and MKAnnotationView.
I have an annotation in the map. When the users tap on it, the callOut Bubble is displayed. When the annotation is tapped again ( and the callOut Bubble is visible ) i need to change to another view.
How can i detect the second tap, or the tap in the bubble?
Could you add a gesture recognizer when you're initializing the MKAnnotationView?
Here's the code for inside dequeueReusableAnnotationViewWithIdentifier:
UITapGestureRecognizer *tapGesture =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(calloutTapped:)];
[theAnnotationView addGestureRecognizer:tapGesture];
[tapGesture release];
The method for the gesture recognizer:
-(void) calloutTapped:(id) sender {
// code to display whatever is required next.
// To get the annotation associated with the callout that caused this event:
// id<MKAnnotation> annotation = ((MKAnnotationView*)sender.view).annotation;
}
Here's the swift version of Dhanu's answer, including getting data from the item selected to pass to the next view controller:
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
let gesture = UITapGestureRecognizer(target: self, action: #selector(MyMapViewController.calloutTapped(_:)))
view.addGestureRecognizer(gesture)
}
func calloutTapped(sender:UITapGestureRecognizer) {
guard let annotation = (sender.view as? MKAnnotationView)?.annotation as? MyAnnotation else { return }
selectedLocation = annotation.myData
performSegueWithIdentifier("mySegueIdentifier", sender: self)
}
To tap the callout button after the user has clicked on the Annotation view, add a UITapGestureRecognizer in didSelectAnnotationView. This way you can implement tap on the callout without needing the accessory views.
You can then get the annotation object back from the sender for further action.
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(calloutTapped:)];
[view addGestureRecognizer:tapGesture];
}
-(void)calloutTapped:(UITapGestureRecognizer *) sender
{
NSLog(#"Callout was tapped");
MKAnnotationView *view = (MKAnnotationView*)sender.view;
id <MKAnnotation> annotation = [view annotation];
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
[self performSegueWithIdentifier:#"annotationDetailSegue" sender:annotation];
}
}
Try to set custom image for button without changing UIButtonTypeDetailDisclosure type.
UIButton *detailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[detailButton setImage:[UIImage imageNamed:#"icon"] forState:UIControlStateNormal];
Here is my solution to this question:
Swift 5
First, add MKMapViewDelegate method
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView)
This gets called when annotation is selected and the callout bubble is shown. You can extract the callout bubble view like this, and add the tap gesture recognizer to it, like so:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
// Set action to callout view
guard let calloutView = view.subviews.first else { return }
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(userDidTapAnnotationCalloutView(_:)))
calloutView.addGestureRecognizer(tapGesture)
}
And handle tap action like this:
#objc
private func userDidTapAnnotationCalloutView(_ sender: UITapGestureRecognizer) {
// Handle tap on callout here
}
This works in Swift 5.2
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let gesture = UITapGestureRecognizer(target: self, action: #selector(calloutTapped))
view.addGestureRecognizer(gesture)
}
#objc func calloutTapped() {
print ("Callout Tapped")
}
Swift 3, using On. You need to handle rightCalloutAccessoryView
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
switch annotation {
case let annotation as Annotation:
let view: AnnotationView = mapView.dequeue(annotation: annotation)
view.canShowCallout = true
let button = UIButton(type: .detailDisclosure)
button.on.tap { [weak self] in
self?.handleTap(annotation: annotation)
}
view.rightCalloutAccessoryView = button
return view
default:
return nil
}
}