Detect Tap on CalloutBubble in MKAnnotationView - ios

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
}
}

Related

Don't animate MKMarkerAnnotationView when selected

I would like to capture taps on an MKMarkerAnnotationView, and not animate the view when this happens.
Through a delegate for my MKMapView, I can capture selection and deselection that are roughly equivalent to taps on the MKMarkerAnnotationView (the selection also happens when tapping the label that is not part of the MKMarkerAnnotationView)
I am trying to remove the default animation. I didn't find a straightforward solution.
I tried:
1/ setting the view to not selected during the selection. This does cancel the animation but further taps are not captured.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
view.setSelected(false, animated: false)
// or
view.isSelected = false
handleTap(view)
}
2/ adding another tap gesture recognizer on the view and preventing other gesture recognizers from receiving the touch. This works well unless I tap the label instead of the annotation view.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let view = dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) as? MKMarkerAnnotationView ??
MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
view.annotation = annotation
view.gestureRecognizers?.forEach { view.removeGestureRecognizer($0) }
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:)))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
return view
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
You can try override class:
final class CustomMarkerAnnotationView: MKMarkerAnnotationView {
var onSelect: (() -> Void)?
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(false, animated: false)
if selected {
onSelect?() // or catch this in delegate
}
}
}
Overriding did not work for me. Here's what ultimately worked for me (Objc):
- (void)mapView:(MKMapView *)mapView
didSelectAnnotationView:(MKAnnotationView *)view
{
//stops animation
for( CALayer *sublayer in view.layer.sublayers )
{
[sublayer removeAllAnimations];
}
//stops selection
[view setSelected:NO animated:NO];
}
For my own purposes I used the below instead, as it allows for properly deselecting the pin when tapping the pin again immediately after selection.
- (void)mapView:(MKMapView *)mapView
didSelectAnnotationView:(MKAnnotationView *)view
{
//stops animation
for( CALayer *sublayer in view.layer.sublayers )
{
[sublayer removeAllAnimations];
}
//stops selection
[mapView deselectAnnotation:view.annotation animated:NO];
}

Swift Annotation Pin's Calloutbubble Gesture

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

Swift - How can I trigger a segue when MKAnnotation button is pressed?

Swift-Newbie question:
I have two view controllers: My home VC which uses MapKit and a second 'MapDetailViewController' to display additional information.
On the home VC, I have added a point of interest with an annotation to display the name of the point and subtitle when the user clicks on the pin. How can I segue to my MapDetailViewController when the user clicks on the map annotation?
// Map view delegate which handles the annotation view
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation:
MKAnnotation) ->
MKAnnotationView? {
guard let annotation = annotation as? Artwork else { return nil }
let identifier = "marker"
var view: MKMarkerAnnotationView
if let dequeuedView =
mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as?
MKMarkerAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKMarkerAnnotationView(annotation: annotation,
reuseIdentifier: identifier)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
// Broke up the button initialisation so that it has unique id 'button'
let button = UIButton(type: .detailDisclosure)
view.rightCalloutAccessoryView = button
// Make the segue happen - tried to make it happen when object 'button' is pressed
performSegue(withIdentifier: "MapDetail", sender: button)
}
return view
}
This is an extract from an old project I was working on. It opens a new tableview controller when the information accessory is tapped on the annotation. I had a made a custom class called Annotation which held all the information about the annotation. Im not sure if this will help :)
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView,
calloutAccessoryControlTapped control: UIControl) {
let annotation = view.annotation as! Annotation
let event = annotation.event
if control == view.rightCalloutAccessoryView {
let destination = self.storyboard!.instantiateViewController(withIdentifier: "EventViewController") as! EventViewController
self.navigationController!.pushViewController(destination, animated: true)
destination.event = event
}
}

Detect on calloutAccessoryControlTapped only the tap on rightCalloutAccessoryView

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.

Get MKPointAnnotation title

I've created a MKMapView which contains several MKPointAnnotations on the map. When the user clicks the UIButton in the view I would like to print the title in the log. How can I do that? So far I have this:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
pinView!.pinColor = .Purple
var rightButton: AnyObject! = UIButton.buttonWithType(UIButtonType.DetailDisclosure)
//MapPointAnnotation *point = (MapPointAnnotation*)pinView.annotation;
//rightButton.venue = point.venue;
rightButton.titleForState(UIControlState.Normal)
rightButton.addTarget(self, action: "rightButtonTapped:", forControlEvents: UIControlEvents.TouchUpInside)
pinView!.rightCalloutAccessoryView = rightButton as UIView
}
else {
pinView!.annotation = annotation
}
return pinView
}
func rightButtonTapped(sender: AnyObject) {
}
In the custom rightButtonTapped method, an easy and reliable way to get a reference to the annotation that was tapped is to use the map view's selectedAnnotations array:
func rightButtonTapped(sender: AnyObject) {
if self.mapView.selectedAnnotations?.count == 0 {
//no annotation selected
return;
}
if let ann = self.mapView.selectedAnnotations[0] as? MKAnnotation {
println("\(ann.title!)")
}
}
(Even though selectedAnnotations is an array, the map view only allows one annotation to be "selected" at a time so the currently selected annotation is always at index 0.)
However, a better way than using a custom button method is to use the map view's calloutAccessoryControlTapped delegate method. The delegate method passes you a reference to the annotation view that was tapped from which you can easily get the underlying annotation.
To use the delegate method, remove the addTarget line for your custom method:
//Do NOT call addTarget if you want to use the calloutAccessoryControlTapped
//delegate method instead of a custom button method.
//rightButton.addTarget(self, action: "rightButtonTapped:", forControlEvents: UIControlEvents.TouchUpInside)
and then implement the delegate method instead of your custom button method:
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
println("\(view.annotation.title!)")
}

Resources