Detect Tap on Calloutbubble in Mkannotationview

Detect Tap on CalloutBubble in MKAnnotationView

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

Detect tap on title of callout

it's little late to the answer your question but I'm dealing with same kind of problem recently and make my solution by my self with trial and error.
maybe I can help someone who dealing with same problem.

you can also use some custom annotation and callout view classes, there are many example out there.
but, there is a another simple way to solve this problem without use the complex foreign classes.

problem definition:
I want to be able to trigger some method just by touching in callout view.
I don't want to use right or left accesory buttons.
but when I touched callout view, view will disapear immediately.

short explanation of solution:
simply we use custom "MKPinAnnotationView" with "hittest" to detect touches in callout view.

you can also find github project link at the end of the post.

touchableCallOutsViewController.h

//
// touchableCallOutsViewController.h
// touchableCallOuts
//
// Created by yasin turkoglu on 20.11.2012.
// Copyright (c) 2012 yasin turkoglu. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>

@class myCustomPinAnnotationClass;

@interface touchableCallOutsViewController : UIViewController <MKMapViewDelegate> {
MKMapView *myMapView;
int pinCounter;
myCustomPinAnnotationClass *myAnnotation;
CLLocationCoordinate2D selectedPinCoordinate;
int selectedPinNumber;
}

@property (strong, nonatomic) MKMapView *myMapView;
@property (strong, nonatomic) myCustomPinAnnotationClass *myAnnotation;
@end

touchableCallOutsViewController.m

//
// touchableCallOutsViewController.m
// touchableCallOuts
//
// Created by yasin turkoglu on 20.11.2012.
// Copyright (c) 2012 yasin turkoglu. All rights reserved.
//

#import "touchableCallOutsViewController.h"
#import "myCustomPinAnnotationClass.h"

@interface touchableCallOutsViewController ()

@end

@implementation touchableCallOutsViewController

@synthesize myMapView;
@synthesize myAnnotation;

- (void)viewDidLoad
{

//first we create mapview and add pin annotation

myMapView = [[MKMapView alloc]initWithFrame:self.view.frame];
myMapView.delegate = self;
MKCoordinateRegion region = {{0,0},{1.0,1.0}};
region.center.latitude = 41.036651; //user defined
region.center.longitude = 28.983870;//user defined
[myMapView setRegion:region animated:YES];
[self.view addSubview:myMapView];

//we define long press recognizer for 2 seconds and add our map view to add new pin when you press 2 seconds on map view
UILongPressGestureRecognizer *lngPrs = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(handleMyLongPress:)];
lngPrs.minimumPressDuration = 2.0;
[myMapView addGestureRecognizer:lngPrs];

pinCounter = 1; //this variable hold pin numbers and increment 1 when new pin added.

myAnnotation = [[myCustomPinAnnotationClass alloc]initWithName:[NSString stringWithFormat:@"Pin %i",pinCounter] description:@"touch here to see pin coordinates" pinNum:pinCounter coordinate:region.center];
[myMapView addAnnotation:myAnnotation];

}

- (void)handleMyLongPress:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateBegan) {

}else{
//when we press more than 2 seconds on map view UILongPressGestureRecognizer triggered this method and call our custom MKPinAnnotationView class and add annotation.
CGPoint touchPoint = [gestureRecognizer locationInView:myMapView];
CLLocationCoordinate2D touchMapCoordinate = [myMapView convertPoint:touchPoint toCoordinateFromView:myMapView];
pinCounter++;
myAnnotation = [[myCustomPinAnnotationClass alloc]initWithName:[NSString stringWithFormat:@"Pin %i",pinCounter] description:@"touch here to see pin coordinates" pinNum:pinCounter coordinate:touchMapCoordinate];
[myMapView addAnnotation:myAnnotation];
}
}

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
//when new annotation added by us, new annotation property will set here
static NSString *identifier = @"myPins";
if ([annotation isKindOfClass:[myCustomPinAnnotationClass class]]) {
myCustomPinAnnotationClass *annotationView = (myCustomPinAnnotationClass *) [self.myMapView dequeueReusableAnnotationViewWithIdentifier:identifier];

if (annotationView == nil) {
annotationView = [[myCustomPinAnnotationClass alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}

myAnnotation = (myCustomPinAnnotationClass *)annotation;

annotationView.pinColor = MKPinAnnotationColorGreen;
annotationView.enabled = YES;
annotationView.draggable = YES;
annotationView.canShowCallout = YES;
[annotationView setSelected:YES animated:YES];
return annotationView;

}
return nil;

}

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{

//if you select any annotation to show their callout sellected annotation pin number and coordinate setted and call findcallot method
myAnnotation = (myCustomPinAnnotationClass *)view.annotation;
selectedPinCoordinate = myAnnotation.coordinate;
selectedPinNumber = myAnnotation.pinNum;
[self findCallOut:myMapView];
}

- (void)findCallOut:(UIView *)node
{
//this method dig our mapview with for loop until find callout view class named "UICalloutView"

if([node isKindOfClass:[NSClassFromString(@"UICalloutView") class]]){

//this method trigered together with callout open animation.

//in UICalloutView we have callout bubble image
//we dig it with for loop to find this image height
float buubleHeight = 0.0;
for(UIImageView *bubbleComponents in node.subviews){
//when we find callout bubble image take height to set our touchable area height
buubleHeight = bubbleComponents.frame.size.height;
break;
}

//this method triger with callout open animation as I said before
//ant this bouncing open animation distort actual size of callout view
//so when this happens we also get transform value of callout view and make proportion to find exact sizes.
CGFloat nodeTransformRatio = node.transform.a;
CGFloat calculatedWidth = roundf(node.frame.size.width / nodeTransformRatio);
CGFloat calculatedHeight = roundf(buubleHeight / nodeTransformRatio);

//now create a new UIView and sized according to callout view.
UIView *touchableView = [[UIView alloc]initWithFrame:CGRectMake(0.0, 0.0, calculatedWidth, calculatedHeight)];
touchableView.userInteractionEnabled = YES;

//we add new single tap gesture recognizer to triger desired method when users touching to the callout
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doSomething)];
[touchableView addGestureRecognizer:singleTap];
//and finaly we add this newly created view in callout view to receive touches.
[node addSubview:touchableView];

}else{
//loop self until find a "UICalloutView"
for(UIView *child in node.subviews){
[self findCallOut:child];
}
}
}

- (void)doSomething
{
//this method call allert view our preseted variables when users touch callout view.
UIAlertView *alertHolder = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:@"You're touched the Pin %i CallOut",selectedPinNumber] message:[NSString stringWithFormat:@"lat : %f\nlong : %f",selectedPinCoordinate.latitude,selectedPinCoordinate.longitude] delegate:self cancelButtonTitle:nil otherButtonTitles:@"ok",nil];
[alertHolder show];

}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

@end

myCustomPinAnnotationClass.h

//
// myCustomPinAnnotationClass.h
// touchableCallOuts
//
// Created by yasin turkoglu on 20.11.2012.
// Copyright (c) 2012 yasin turkoglu. All rights reserved.
//

#import <MapKit/MapKit.h>

@interface myCustomPinAnnotationClass : MKPinAnnotationView <MKAnnotation> {
NSString *_name;
NSString *_description;
int _pinNum;
CLLocationCoordinate2D _coordinate;
}

@property (copy) NSString *name;
@property (copy) NSString *description;
@property (nonatomic, readonly) int pinNum;
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;

- (id)initWithName:(NSString*)header description:(NSString*)description pinNum:(int)pinNum coordinate:(CLLocationCoordinate2D)coordinate;

@end

myCustomPinAnnotationClass.m

//
// myCustomPinAnnotationClass.m
// touchableCallOuts
//
// Created by yasin turkoglu on 20.11.2012.
// Copyright (c) 2012 yasin turkoglu. All rights reserved.
//

#import "myCustomPinAnnotationClass.h"

@implementation myCustomPinAnnotationClass

@synthesize name = _name;
@synthesize description = _description;
@synthesize pinNum = _pinNum;
@synthesize coordinate = _coordinate;

- (id)initWithName:(NSString*)header description:(NSString*)description pinNum:(int)pinNum coordinate:(CLLocationCoordinate2D)coordinate
{
self = [super init];
if (self) {
_name = [header copy];
_description = [description copy];
_pinNum = pinNum;
_coordinate = coordinate;
}
return self;
}

- (void)setCoordinate:(CLLocationCoordinate2D)newCoordinate
{
_coordinate = newCoordinate;
}

- (NSString *)title {
return _name;
}

- (NSString *)subtitle {
return _description;
}

- (int)tag {
return _pinNum;
}

-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
//test touched point in map view
//when hit test return nil callout close immediately by default
UIView* hitView = [super hitTest:point withEvent:event];
// if hittest return nil test touch point
if (hitView == nil){
//dig view to find custom touchable view lately added by us
for(UIView *firstView in self.subviews){
if([firstView isKindOfClass:[NSClassFromString(@"UICalloutView") class]]){
for(UIView *touchableView in firstView.subviews){
if([touchableView isKindOfClass:[UIView class]]){ //this is our touchable view class
//define touchable area
CGRect touchableArea = CGRectMake(firstView.frame.origin.x, firstView.frame.origin.y, touchableView.frame.size.width, touchableView.frame.size.height);
//test touch point if in touchable area
if (CGRectContainsPoint(touchableArea, point)){
//if touch point is in touchable area return touchable view as a touched view
hitView = touchableView;
}
}
}
}
}
}
return hitView;
}

@end

Here you can download complete project listing with source code https://github.com/ytur/touchableCallOutsForIOSMaps

Detect on calloutAccessoryControlTapped only the tap on rightCalloutAccessoryView

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 {

}

MKAnnotationView and tap detection

There might be a better and cleaner solution but one way to do the trick is exploiting hitTest:withEvent: in the tap gesture recognized selector, e.g.

suppose you have added a tap gesture recognizer to your _mapView

- (void)tapped:(UITapGestureRecognizer *)g
{
CGPoint p = [g locationInView:_mapView];
UIView *v = [_mapView hitTest:p withEvent:nil];

if (v == subviewOfKindOfClass(_mapView, @"MKAnnotationContainerView"))
NSLog(@"tap on the map"); //put your action here
}

// depth-first search
UIView *subviewOfKindOfClass(UIView *view, NSString *className)
{
static UIView *resultView = nil;

if ([view isKindOfClass:NSClassFromString(className)])
return view;

for (UIView *subv in [view subviews]) {
if ((resultView = subviewOfKindOfClass(subv, className)) break;
}
return resultView;
}

It's probably doesn't cover all the edge cases but it seems to work pretty well for me.

UPDATE (iOS >= 6.0)

Finally, I found another kind of solution which has the drawback of being valid only for iOS >= 6.0: In fact, this solution exploits the new -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer added to the UIViews in this way

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// overrides the default value (YES) to have gestureRecognizer ignore the view
return NO;
}

I.e., from the iOS 6 onward, it's sufficient to override that UIView method in each view the gesture recognizer should ignore.

Having to tap outside of an MKAnnotationView for the callout bubble

While using a subview to implement cornerRadius and callOut, I managed to align the image and touch area by messing around with anchorPoint. The only catch is that the touch radius is like that of the original MKAnnotationPin, so the user has to be precise about where to tap. Otherwise, the best method (and the most tedious) is to subclass MKAnnotationView and override the drawRect method to draw a circular mask for the image. To make life easier, I found a library called Toucans that does this sort of functionality.

imageView.layer.anchorPoint = CGPoint(x: 1, y: 1)

Prevent touch events on MKMapView being detected when a MKAnnotation is tapped

I think you will find the following links very useful:

http://blog.asynchrony.com/2010/09/building-custom-map-annotation-callouts-part-2/

How do I make a MKAnnotationView touch sensitive?

The first link discusses (among other things) how to prevent the propagation of touches to the annotations so that they selectively respond, and the second one how to detect the touches.



Related Topics



Leave a reply



Submit