MKMapView
。我添加了一个单击手势UITapGestureRecognizer
。
现在我想在地图上添加一个MKAnnotationView
。我可以点击注释并触发mapView:mapView didSelectAnnotationView:view
(这是我将添加附加逻辑以显示UIView的地方)。
问题在于,现在当我点击注释时,MKMapView
的触摸手势也会触发。
我能否设置仅在点击注释时响应它?
MKMapView
。我添加了一个单击手势UITapGestureRecognizer
。
现在我想在地图上添加一个MKAnnotationView
。我可以点击注释并触发mapView:mapView didSelectAnnotationView:view
(这是我将添加附加逻辑以显示UIView的地方)。
问题在于,现在当我点击注释时,MKMapView
的触摸手势也会触发。
我能否设置仅在点击注释时响应它?
可能有更好、更清晰的解决方案,但一个做法是在点按手势识别器的选择器中利用 hitTest:withEvent:
方法,例如:
假设您已经将一个点按手势识别器添加到了您的 _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;
}
也许它不能覆盖所有的边缘情况,但对我来说似乎工作得相当不错。
更新(iOS >= 6.0)
最后,我发现另一种解决方法,它的缺点是只适用于= 6.0 : 实际上,这个解决方案利用了在UIView
中添加的新的 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// overrides the default value (YES) to have gestureRecognizer ignore the view
return NO;
}
自iOS 6起,只需在每个视图中重写UIView
方法,即可使手势识别器忽略该视图。
您的解决方案应该利用委托上的- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
方法。
在此方法中,您可以检查触摸是否在您的注释之一上,如果是,则return NO
,以便您的gestureRecognizer
不会被激活。
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
}
-(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);
}
- (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;
}
这段代码还考虑了注释周围的一些接近触摸(因为正如所说,MKMapView也接受接近触摸,不仅仅是正确的触摸):
我添加了Log函数,这样你就可以在控制台观察并理解问题。
- (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.x-p.x), (anView.frame.origin.y-p.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;
}
我的注释框大小为25x25,所以我接受30的距离。您可以应用您的逻辑,例如 if (p.x >= anView.frame.origin.x - 6) && Y 等等。
如果我们只测试手势委托中touch.view
的父视图,那会容易得多:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
var tv = touch.view
while let view = tv, !(view is MKAnnotationView) {
tv = view.superview
}
return tv == nil
}
我不确定为什么你会在地图视图上使用 UITapGestureRecognizer
,明显地说出来意味着它会干扰地图的一些多点触控功能。
我建议你查看并尝试使用 UIGestureRecognizer
的 cancelsTouchesInView
属性(参见文档)。我认为这可以解决你的问题。确保你仔细阅读文档。
UITapGestureRecognizer
允许用户选择一个多边形对象并对其进行操作。 - Padin215MKAnnotationView
上添加了一个 UITapGestureRecognzier
并设置了 cancelsTouchesInView
。但它并没有阻止 MKMapView
上的点击识别器触发。 - Padin215
MKAnnotationContainerView
是什么。我本来期望UIView *v
的hitTest是一个MKMapView,但惊讶地发现它不是。非常感谢您的帮助,我真的很感激。顺便问一下,您使用xCode吗?我对第二个方法头不熟悉。 - Padin215MKAnnotationContainerView
应该是苹果私有类,其实例应包含MKMapView
的 annotationView。 我猜这就是为什么hitTest
返回它而不是MKMapView
的原因。 关于你的好奇心,是的,我使用 Xcode,可能这个签名对你来说听起来有些奇怪,因为它与 obj C 实际上基于的 C 语言有关。 - HepaKKes