【问题标题】:How to customize the callout bubble for MKAnnotationView?如何自定义 MKAnnotationView 的标注气泡?
【发布时间】:2010-12-06 15:42:11
【问题描述】:

我目前正在使用 mapkit 并且卡住了。

我有一个正在使用的自定义注释视图,我想使用 image 属性在地图上用我自己的图标显示点。我有这个工作正常。但我还想做的是覆盖默认标注视图(触摸注释图标时显示的标题/副标题的气泡)。我希望能够控制标注本身:mapkit 仅提供对左右辅助标注视图的访问,但无法为标注气泡提供自定义视图,或将其设为零大小或其他任何内容。

我的想法是覆盖我的MKMapViewDelegate 中的selectAnnotation/deselectAnnotation,然后通过调用我的自定义注释视图来绘制我自己的自定义视图。这有效,但仅当在我的自定义注释视图类中将 canShowCallout 设置为 YES 时。如果我将此设置为NO(这是我想要的,因此不会绘制默认标注气泡),则不会调用这些方法。因此,如果没有显示默认标注气泡视图,我无法知道用户是触摸了我在地图上的点(选择了它)还是触摸了不属于我的注释视图的点(选择了它)。

我尝试走一条不同的路,自己处理地图中的所有触摸事件,但我似乎无法正常工作。我阅读了与在地图视图中捕获触摸事件相关的其他帖子,但它们并不是我想要的。有没有办法在绘制之前深入地图视图以删除标注气泡?我很茫然。

有什么建议吗?我错过了什么明显的东西吗?

【问题讨论】:

标签: ios objective-c mapkit mkmapview mkannotationview


【解决方案1】:

detailCalloutAccessoryView

在过去这很痛苦,但 Apple 已经解决了,只需查看 MKAnnotationView 上的文档即可

view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.detailCalloutAccessoryView = UIImageView(image: UIImage(named: "zebra"))

真的,就是这样。接受任何 UIView。

【讨论】:

  • 哪个最好用?
【解决方案2】:

我遇到了同样的问题。在这个博客http://spitzkoff.com/craig/?p=81 上有很多关于这个主题的博文。

在这里仅使用 MKMapViewDelegate 对您没有帮助,子类化 MKMapView 并尝试扩展现有功能也对我不起作用。

我最终做的是创建我自己的CustomCalloutView,并在我的MKMapView 之上。你可以用任何你想要的方式来设置这个视图的样式。

我的CustomCalloutView有一个类似这个的方法:

- (void) openForAnnotation: (id)anAnnotation { self.annotation = anAnnotation; // remove from view [self removeFromSuperview]; titleLabel.text = self.annotation.title; [self updateSubviews]; [self updateSpeechBubble]; [self.mapView addSubview: self]; }

它接受一个MKAnnotation 对象并设置它自己的标题,然后它调用另外两个非常丑陋的方法来调整标注内容的宽度和大小,然后在它周围的正确位置绘制对话气泡。

最后将视图作为子视图添加到 mapView。这个解决方案的问题是当地图视图滚动时很难将标注保持在正确的位置。我只是将标注隐藏在区域更改的地图视图委托方法中以解决此问题。

解决所有这些问题需要一些时间,但现在标注的行为几乎与官方标注一样,但我有自己的风格。

【讨论】:

  • 我第二点是:UICalloutView 是一个私有类,SDK 中没有官方提供。你最好听从 Sascha 的建议。
  • 嗨,萨沙,我们感谢您的回复。但是,我们目前遇到的问题是如何在地图上获取大头针出现的位置(x,y,而不是纬度或经度),以便我们可以显示标注视图。
  • 坐标转换可以通过MKMapView的两个函数轻松完成:# – convertCoordinate:toPointToView: # – convertPoint:toCoordinateFromView:
【解决方案3】:

我只是想出了一个办法,这里的思路是

  // Detect the touch point of the AnnotationView ( i mean the red or green pin )
  // Based on that draw a UIView and add it to subview.
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
    CGPoint newPoint = [self.mapView convertCoordinate:selectedCoordinate toPointToView:self.view];
//    NSLog(@"regionWillChangeAnimated newPoint %f,%f",newPoint.x,newPoint.y);
    [testview  setCenter:CGPointMake(newPoint.x+5,newPoint.y-((testview.frame.size.height/2)+35))];
    [testview setHidden:YES];
}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    CGPoint newPoint = [self.mapView convertCoordinate:selectedCoordinate toPointToView:self.view];
//    NSLog(@"regionDidChangeAnimated newPoint %f,%f",newPoint.x,newPoint.y);
    [testview  setCenter:CGPointMake(newPoint.x,newPoint.y-((testview.frame.size.height/2)+35))];
    [testview setHidden:NO];
}

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view 
{  
    NSLog(@"Select");
    showCallout = YES;
    CGPoint point = [self.mapView convertPoint:view.frame.origin fromView:view.superview];
    [testview setHidden:NO];
    [testview  setCenter:CGPointMake(point.x+5,point.y-(testview.frame.size.height/2))];
    selectedCoordinate = view.annotation.coordinate;
    [self animateIn];
}

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view 
{
    NSLog(@"deSelect");
    if(!showCallout)
    {
        [testview setHidden:YES];
    }
}

这里 - testview 是大小为 320x100 的 UIView - showCallout 是 BOOL - [self animateIn]; 是像 UIAlertView 这样查看动画的函数。

【讨论】:

  • selectedCoordinate 指定了什么?那是什么类型的对象?
  • 它是 CLLocationCoordinate2d 对象。
【解决方案4】:

发现这对我来说是最好的解决方案。 您必须使用一些创造力来进行自己的自定义

在您的MKAnnotationView 子类中,您可以使用

- (void)didAddSubview:(UIView *)subview{
    int image = 0;
    int labelcount = 0;
    if ([[[subview class] description] isEqualToString:@"UICalloutView"]) {
        for (UIView *subsubView in subview.subviews) {
            if ([subsubView class] == [UIImageView class]) {
                UIImageView *imageView = ((UIImageView *)subsubView);
                switch (image) {
                    case 0:
                        [imageView setImage:[UIImage imageNamed:@"map_left"]];
                        break;
                    case 1:
                        [imageView setImage:[UIImage imageNamed:@"map_right"]];
                        break;
                    case 3:
                        [imageView setImage:[UIImage imageNamed:@"map_arrow"]];
                        break;
                    default:
                        [imageView setImage:[UIImage imageNamed:@"map_mid"]];
                        break;
                }
                image++;
            }else if ([subsubView class] == [UILabel class]) {
                UILabel *labelView = ((UILabel *)subsubView);
                switch (labelcount) {
                    case 0:
                        labelView.textColor = [UIColor blackColor];
                        break;
                    case 1:
                        labelView.textColor = [UIColor lightGrayColor];
                        break;

                    default:
                        break;
                }
                labelView.shadowOffset = CGSizeMake(0, 0);
                [labelView sizeToFit];
                labelcount++;
            }
        }
    }
}

如果subviewUICalloutView,那么您可以随意使用它,以及其中的内容。

【讨论】:

  • 如何检查子视图是否为 UICalloutView,因为 UICalloutview 不是公共类。
  • if ([[[subview class] description] isEqualToString:@"UICalloutView"]) 我认为有更好的方法,但是这个方法可行。
  • 你对此有什么问题吗,因为正如 Manish 指出的那样,这是一个私人课程。
  • 可能他们更改了标注视图的 UIView 结构。我还没有升级使用它的应用程序,所以你自己:P
【解决方案5】:

还有一个更简单的解决方案。

创建一个自定义 UIView(用于您的标注)。

然后创建MKAnnotationView的子类并覆盖setSelected如下:

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];

    if(selected)
    {
        //Add your custom view to self...
    }
    else
    {
        //Remove your custom view...
    }
}

繁荣,工作完成。

【讨论】:

  • 嗨 TappCandy!首先,感谢您的解决方案。它确实有效,但是当加载视图(我是从 nib 文件中执行此操作)时,按钮不起作用。我该怎么做才能使它们正常工作?谢谢
  • 喜欢这个解决方案的简单性!
  • 嗯——这行不通!它取代了地图注释(即图钉)而不是标注“气泡”。
  • 这行不通。 MKAnnotationView 没有对地图视图的引用,因此您在添加自定义标注时遇到了几个问题。例如,您不知道将其添加为子视图是否会在屏幕外。
  • @PapillonUK 您可以控制标注的框架。它没有替换销,它出现在它的顶部。将其添加为子视图时调整其位置。
【解决方案6】:

我推出了出色的 SMCalloutView 的分支,它通过为标注提供自定义视图并允许灵活的宽度/高度非常轻松地解决了这个问题。仍然有一些怪癖需要解决,但到目前为止它非常实用:

https://github.com/u10int/calloutview

【讨论】:

    【解决方案7】:

    可以使用leftCalloutView,设置annotation.text为@" "

    请在下面找到示例代码:

    pinView = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
    if(pinView == nil){
        pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID] autorelease];       
    }
    CGSize sizeText = [annotation.title sizeWithFont:[UIFont fontWithName:@"HelveticaNeue" size:12] constrainedToSize:CGSizeMake(150, CGRectGetHeight(pinView.frame))                                 lineBreakMode:UILineBreakModeTailTruncation];
    pinView.canShowCallout = YES;    
    UILabel *lblTitolo = [[UILabel alloc] initWithFrame:CGRectMake(2,2,150,sizeText.height)];
    lblTitolo.text = [NSString stringWithString:ann.title];
    lblTitolo.font = [UIFont fontWithName:@"HelveticaNeue" size:12];
    lblTitolo.lineBreakMode = UILineBreakModeTailTruncation;
    lblTitolo.numberOfLines = 0;
    pinView.leftCalloutAccessoryView = lblTitolo;
    [lblTitolo release];
    annotation.title = @" ";            
    

    【讨论】:

    • 这在一定程度上会“起作用”,但并不能真正回答所提出的更一般的问题,而且非常hackish。
    【解决方案8】:

    继续@TappCandy 的出色简单答案,如果您想以与默认设置相同的方式为气泡设置动画,我制作了此动画方法:

    - (void)animateIn
    {   
        float myBubbleWidth = 247;
        float myBubbleHeight = 59;
    
        calloutView.frame = CGRectMake(-myBubbleWidth*0.005+8, -myBubbleHeight*0.01-2, myBubbleWidth*0.01, myBubbleHeight*0.01);
        [self addSubview:calloutView];
    
        [UIView animateWithDuration:0.12 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^(void) {
            calloutView.frame = CGRectMake(-myBubbleWidth*0.55+8, -myBubbleHeight*1.1-2, myBubbleWidth*1.1, myBubbleHeight*1.1);
        } completion:^(BOOL finished) {
            [UIView animateWithDuration:0.1 animations:^(void) {
                calloutView.frame = CGRectMake(-myBubbleWidth*0.475+8, -myBubbleHeight*0.95-2, myBubbleWidth*0.95, myBubbleHeight*0.95);
            } completion:^(BOOL finished) {
                [UIView animateWithDuration:0.075 animations:^(void) {
                    calloutView.frame = CGRectMake(-round(myBubbleWidth/2-8), -myBubbleHeight-2, myBubbleWidth, myBubbleHeight);
                }];
            }];
        }];
    }
    

    它看起来相当复杂,但只要你的标注气泡的点被设计为中心底部,你应该能够用你自己的大小替换 myBubbleWidthmyBubbleHeight 以使其工作。请记住确保您的子视图将其 autoResizeMask 属性设置为 63(即“全部”),以便它们在动画中正确缩放。

    :-乔

    【讨论】:

    • 顺便说一句,您可以在 scale 属性而不是 frame 上进行动画处理,以便正确缩放内容。
    • 不要使用 CGRectMake。使用 CGTransform。
    • @occulus - 我想我首先尝试过,但在 iOS 4 中出现了渲染问题。但这可能是因为我没有使用CABasicAnimation... 太久以前记不得了!我知道由于中心偏移,我还必须为 y 属性设置动画。
    • @CameronLowellPalmer - 为什么不使用CGRectMake
    • @jowie 因为语法更好,并且避免了代码中的魔法值。 CGAffineTransformMakeScale(1.1f, 1.1f);将盒子扩大 110% 是显而易见的。你的做法可能行得通,但并不漂亮。
    【解决方案9】:

    基本上要解决这个问题,需要: a) 防止出现默认标注气泡。 b) 找出点击了哪个注释。

    我能够通过以下方式实现这些目标: a) 将 canShowCallout 设置为 NO b) 子类化、MKPinAnnotationView 并覆盖 touchesBegan 和 touchesEnd 方法。

    注意:您需要处理 MKAnnotationView 而不是 MKMapView 的触摸事件

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-02-01
      • 2015-03-19
      • 2011-12-22
      • 2013-07-20
      • 1970-01-01
      • 2010-11-23
      • 2015-07-19
      相关资源
      最近更新 更多