【问题标题】:MKAnnotationView and tap detectionMKAnnotationView 和点击检测
【发布时间】:2013-06-16 13:24:09
【问题描述】:

我有一个MKMapView。我点击一下就添加了UITapGestureRecognizer

我现在想在地图上添加一个MKAnnotationView。我可以点击注释并触发mapView:mapView didSelectAnnotationView:view(我将在其中添加其他逻辑以显示 UIView)。

现在的问题是,当我点击注释时,MKMapView 的点击手势也会触发。

我可以设置它,如果我点击注释,它只会响应吗?

【问题讨论】:

    标签: ios objective-c mkmapview mkannotation mkannotationview


    【解决方案1】:

    可能有更好、更简洁的解决方案,但一种方法是在点击手势识别选择器中利用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)

    最后,我发现了另一种解决方案,其缺点是仅对iOS >= 6.0 有效:事实上,该解决方案利用了添加到UIViews 中的新-(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 吗?我对第二种方法头不熟悉。
    • 如果我理解得很好,MKAnnotationContainerView 应该是一个苹果私有类,其实例应该包含MKMapView 的 annotationViews。我想这就是hitTest 返回它而不是MKMapView 的原因。关于您的好奇心,是的,我使用 xcode,可能该签名对您来说听起来有些奇怪,因为它与 obj C 实际基于的 C 语言有关。
    • Hrm...如果它是私人课程,我尝试上传时苹果会出现问题吗?
    • 我不这么认为,因为您的应用没有尝试操作该对象或使用私有 API,所以应该没问题。有关更多详细信息,请访问link
    • 我们不应该知道MKAnnotationContainerView,如果Apple决定改变这个API怎么办?到目前为止,他们实际上已经拥有,现在称为 MKNewAnnotationContainerView。
    【解决方案2】:

    您的解决方案应该在您的委托上使用- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch 方法。

    在此方法中,您可以检查触摸是否在您的一个注释上,如果是,则 return NO 以便您的 gestureRecognizer 不会被激活。

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

    斯威夫特:

    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
    }
    

    【讨论】:

      【解决方案3】:

      为什么不在viewForAnnotation中添加UITapGestureRecognizer,使用注解的reuseIdentifier来识别它是哪个注解,在tapGestureRecognizer action方法中你可以访问那个标识符。

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

      【讨论】:

        【解决方案4】:

        警告!公认的解决方案以及下面的解决方案有时有点错误。为什么?有时您点击注释,但您的代码会像您点击地图一样。这是什么原因?因为您点击了注释框架周围的某个位置,例如 +- 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 也接受接近触摸,而不仅仅是正确的触摸):

        我包含了日志功能,以便您可以在控制台中观看并了解问题。

        - (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 等逻辑。

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

            我不知道为什么你的地图视图上会有UITapGestureRecognizer,用纯文本说这显然意味着它会干扰你地图的一些多点触控功能。

            我建议您看一下并使用UIGestureRecognizercancelsTouchesInView 属性(请参阅documentation)。我认为这可以解决您的问题。请务必查看文档。

            【讨论】:

            • 我在用户交互的地图上绘制了 MKPolygon。 UITapGestureRecognizer 允许用户选择一个多边形对象并用它做一些事情。
            • 好的,我在MKAnnotationView 中添加了UITapGestureRecognzier 并设置了cancelsTouchesInView。它并没有阻止MKMapView 上的点击识别器触发。
            猜你喜欢
            • 2011-03-24
            • 2016-10-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-08-25
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多