MKAnnotationView和触摸检测

9
我有一个 MKMapView。我添加了一个单击手势UITapGestureRecognizer。 现在我想在地图上添加一个MKAnnotationView。我可以点击注释并触发mapView:mapView didSelectAnnotationView:view (这是我将添加附加逻辑以显示UIView的地方)。 问题在于,现在当我点击注释时,MKMapView的触摸手势也会触发。 我能否设置仅在点击注释时响应它?
6个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
6

可能有更好、更清晰的解决方案,但一个做法是在点按手势识别器的选择器中利用 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方法,即可使手势识别器忽略该视图。

好的。这个可行。但我不是完全确定MKAnnotationContainerView是什么。我本来期望UIView *v的hitTest是一个MKMapView,但惊讶地发现它不是。非常感谢您的帮助,我真的很感激。顺便问一下,您使用xCode吗?我对第二个方法头不熟悉。 - Padin215
如果我理解正确的话,MKAnnotationContainerView 应该是苹果私有类,其实例应包含 MKMapView 的 annotationView。 我猜这就是为什么 hitTest 返回它而不是 MKMapView 的原因。 关于你的好奇心,是的,我使用 Xcode,可能这个签名对你来说听起来有些奇怪,因为它与 obj C 实际上基于的 C 语言有关。 - HepaKKes
嗯...如果这是一个私有类,那么当我尝试上传时,苹果会有问题吗? - Padin215
我认为不会有问题,因为您的应用程序并没有试图操纵该对象或使用私有API。如需进一步了解,请访问此链接:https://dev59.com/J3A75IYBdhLWcg3wo6vx - HepaKKes
我们不应该知道MKAnnotationContainerView的存在,如果苹果决定更改这个API会怎样呢? 目前他们已经更改了,现在它被称为MKNewAnnotationContainerView。 - Rodrigo Ruiz
@RodrigoRuiz 我一开始回答时说这可能不是最好的解决方案...这就是为什么我进一步编辑了我的答案。如果你有更优雅和强大的解决方案,请与我们分享 :) - HepaKKes

4

您的解决方案应该利用委托上的- (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
}

2
为什么不在viewForAnnotation中添加UITapGestureRecognazer,使用注释的重用标识符来识别它是哪个注释,在tapGestureRecognizer操作方法中,您可以访问该标识符。
-(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
警告!接受的解决方案和下面的一个有时会有一些错误。为什么?有时您点击注释,但您的代码会像您点击地图一样操作。这是什么原因?因为您在注释框架周围点击了大约1-6个像素的位置,而不是在注释视图的框架内部。 有趣的是,尽管您的代码在这种情况下会说“您点击了地图,而不是注释”,但MKMapView上的默认代码逻辑也会接受这种接近的点击,就像它在注释区域内一样,并触发didSelectAnnotation事件。 因此,您还必须在您的代码中反映这个问题。 假设这是默认代码:
- (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 等等。


0

如果我们只测试手势委托中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
}

-1

我不确定为什么你会在地图视图上使用 UITapGestureRecognizer,明显地说出来意味着它会干扰地图的一些多点触控功能。

我建议你查看并尝试使用 UIGestureRecognizercancelsTouchesInView 属性(参见文档)。我认为这可以解决你的问题。确保你仔细阅读文档。


我在地图上绘制MKPolygon,用户可以与之交互。UITapGestureRecognizer允许用户选择一个多边形对象并对其进行操作。 - Padin215
好的,我在 MKAnnotationView 上添加了一个 UITapGestureRecognzier 并设置了 cancelsTouchesInView。但它并没有阻止 MKMapView 上的点击识别器触发。 - Padin215

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,