MKaNnotationView and tap detection

I have MKMapView . I added UITapGestureRecognizer one click.

Now I want to add MKAnnotationView to the map. I can use annotation and mapView:mapView didSelectAnnotationView:view fires (where I will add additional logic to display the UIView).

The problem arises when I click on the annotation, also burns the MKMapView screen MKMapView .

Can I set it like this if I click on the annotation, it only answers?

+8
ios objective-c mkmapview mkannotationview mkannotation
source share
5 answers

There might be a better and cleaner solution, but one way to do the trick is to use hitTest:withEvent: in the hard drive recognition selector, for example.

suppose you add a 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 probably does not cover all extreme cases, but it seems to be very good for me.

UPDATE (iOS> = 6.0)

Finally, I found another solution that has a flaw in action only for iOS> = 6.0: in fact, this solution uses the new -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer added to the UIView in this way

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

Ie, starting with iOS 6, it is enough to override this UIView method in each view, which the gesture recognizer should ignore.

+5
source share

Your solution should use the - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch for your delegate.

In this method, you can check if one of your annotations was gestureRecognizer , and if so, return NO so that your gestureRecognizer not activated.

Objective-C:

 - (NSArray*)getTappedAnnotations:(UITouch*)touch { NSMutableArray* tappedAnnotations = [NSMutableArray array]; for(id<MKAnnotation> annotation in self.mapView.annotations) { MKAnnotationView* view = [self.mapView viewForAnnotation:annotation]; CGPoint location = [touch locationInView:view]; if(CGRectContainsPoint(view.bounds, location)) { [tappedAnnotations addObject:view]; } } return tappedAnnotations; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { return [self getTappedAnnotations:touch].count > 0; } 

Swift:

 private func getTappedAnnotations(touch touch: UITouch) -> [MKAnnotationView] { var tappedAnnotations: [MKAnnotationView] = [] for annotation in self.mapView.annotations { if let annotationView: MKAnnotationView = self.mapView.viewForAnnotation(annotation) { let annotationPoint = touch.locationInView(annotationView) if CGRectContainsPoint(annotationView.bounds, annotationPoint) { tappedAnnotations.append(annotationView) } } } return tappedAnnotations } func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool { return self.getTappedAnnotations(touch: touch).count > 0 } 
+4
source share

Why not just add a UITapGestureRecognazer to viewForAnnotation, use the annotation reuseIdentifier to determine which annotation is there, and in the tapGestureRecognizer action method you can access that identifier.

 -(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { MKAnnotationView *ann = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:@"some id"]; if (ann) { return ann; } ann = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"some id"]; ann.enabled = YES; UITapGestureRecognizer *pinTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pinTapped:)]; [ann addGestureRecognizer:pinTap]; } -(IBAction)pinTapped:(UITapGestureRecognizer *)sender { MKAnnotationView *pin = (MKPinAnnotationView *)sender.view; NSLog(@"Pin with id %@ tapped", pin.reuseIdentifier); } 
+1
source share

I'm not sure why you will have a UITapGestureRecognizer on your map view, stating that this in plain text obviously implies that it will mess with some of the multitouch features of your map.

I suggest you take a look and play with the cancelsTouchesInView UIGestureRecognizer property (see documentation ). I think this may solve your problem. Make sure you check the documentation.

0
source share

Attention! The decision made, as well as one of the following, is sometimes erroneous. What for? Sometimes you click on the annotation, but your code will act as if you were using a map. What is the reason for this? Because you listened somewhere around your annotation frame, for example, + 1-6 pixels around, but not within the annotation frame.

It is also interesting that although your code says “you typed a card, not an annotation” in this case, the default code logic on MKMapView will also accept this close tap, as if it were in the annotation area and didSelectAnnotation would fire.

So, you should reflect this problem in your code as well. Let's say this is the default code:

 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { CGPoint p = [gestureRecognizer locationInView:_customMapView]; UIView *v = [_customMapView hitTest:p withEvent:nil]; if (![v isKindOfClass:[MKAnnotationView class]]) { return YES; // annotation was not tapped, let the recognizer method fire } return NO; } 

And this code also takes into account some proximity affects annotations (because, as said, MKMapView also accepts proximity, not just the right touches):

I turned on Log functions so you can look at it in the console and understand the problem.

 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { CGPoint p = [gestureRecognizer locationInView:_customMapView]; NSLog(@"point %@", NSStringFromCGPoint(p)); UIView *v = [_customMapView hitTest:p withEvent:nil]; if (![v isKindOfClass:[MKAnnotationView class]]) { // annotation was not tapped, be we will accept also some // proximity touches around the annotations rects for (id<MKAnnotation>annotation in _customMapView.annotations) { MKAnnotationView* anView = [_customMapView viewForAnnotation: annotation]; double dist = hypot((anView.frame.origin.xp.x), (anView.frame.origin.yp.y)); // compute distance of two points NSLog(@"%@ %f %@", NSStringFromCGRect(anView.frame), dist, [annotation title]); if (dist <= 30) return NO; // it was close to some annotation se we believe annotation was tapped } return YES; } return NO; } 

My annotation block is 25x25 in size, so I take a distance of 30. You can apply your logic as if (px> = anView.frame.origin.x - 6) && & && Y, etc.

0
source share

All Articles