ios 如何为 MKAnnotationView 自定义标注气泡?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1565828/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to customize the callout bubble for MKAnnotationView?
提问by Zach
I'm currently working with the mapkit and am stuck.
我目前正在使用 mapkit 并且被卡住了。
I have a custom annotation view I am using, and I want to use the image property to display the point on the map with my own icon. I have this working fine. But what I would also like to do is to override the default callout view (the bubble that shows up with the title/subtitle when the annotation icon is touched). I want to be able to control the callout itself: the mapkit only provides access to the left and right ancillary callout views, but no way to provide a custom view for the callout bubble, or to give it zero size, or anything else.
我有一个正在使用的自定义注释视图,我想使用 image 属性在地图上用我自己的图标显示点。我有这个工作正常。但我还想做的是覆盖默认标注视图(触摸注释图标时显示标题/副标题的气泡)。我希望能够控制标注本身:mapkit 仅提供对左右辅助标注视图的访问,但无法为标注气泡提供自定义视图,或将其设为零大小或其他任何内容。
My idea was to override selectAnnotation/deselectAnnotation in my MKMapViewDelegate
, and then draw my own custom view by making a call to my custom annotation view. This works, but only when canShowCallout
is set to YES
in my custom annotation view class. These methods are NOT called if I have this set to NO
(which is what I want, so that the default callout bubble is not drawn). So I have no way of knowing if the user touched on my point on the map (selected it) or touched a point that is not part of my annotation views (delected it) without having the default callout bubble view show up.
我的想法是在 my 中覆盖 selectAnnotation/deselectAnnotation MKMapViewDelegate
,然后通过调用我的自定义注释视图来绘制我自己的自定义视图。这有效,但仅当在我的自定义注释视图类中canShowCallout
设置YES
为时。如果我将此设置为NO
(这是我想要的,因此不会绘制默认标注气泡),则不会调用这些方法。因此,如果没有显示默认标注气泡视图,我无法知道用户是否触摸了我在地图上的点(选择了它)或触摸了不属于我的注释视图的点(选择了它)。
I tried going down a different path and just handling all touch events myself in the map, and I can't seem to get this working. I read other posts related to catching touch events in the map view, but they aren't exactly what I want. Is there a way to dig into the map view to remove the callout bubble before drawing? I'm at a loss.
我尝试沿着不同的路径走,只是自己在地图中处理所有触摸事件,但我似乎无法使其正常工作。我阅读了与在地图视图中捕获触摸事件相关的其他帖子,但它们并不是我想要的。有没有办法在绘制之前深入地图视图以删除标注气泡?我不知所措。
Any suggestions? Am I missing something obvious?
有什么建议?我错过了一些明显的东西吗?
回答by TappCandy
There is an even easier solution.
有一个更简单的解决方案。
Create a custom UIView
(for your callout).
创建自定义UIView
(为您的标注)。
Then create a subclass of MKAnnotationView
and override setSelected
as follows:
然后创建一个子类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...
}
}
Boom, job done.
轰隆隆,大功告成。
回答by Cameron Lowell Palmer
detailCalloutAccessoryView
detailCalloutAccessoryView
In the olden days this was a pain, but Apple has solved it, just check the docs on MKAnnotationView
在过去,这很痛苦,但 Apple 已经解决了,只需查看MKAnnotationView上的文档
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.detailCalloutAccessoryView = UIImageView(image: UIImage(named: "zebra"))
Really, that's it. Takes any UIView.
真的,就是这样。接受任何 UIView。
回答by jowie
Continuing on from @TappCandy's brilliantly simple answer, if you want to animate your bubble in the same way as the default one, I've produced this animation method:
继续@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);
}];
}];
}];
}
It looks fairly complicated, but as long as the point of your callout bubble is designed to be centre-bottom, you should just be able to replace myBubbleWidth
and myBubbleHeight
with your own size for it to work. And remember to make sure your subviews have their autoResizeMask
property set to 63 (i.e. "all") so that they scale correctly in the animation.
它看起来相当复杂,但只要标注气泡的点设计为中心底部,您就应该能够替换myBubbleWidth
并myBubbleHeight
使用您自己的尺寸使其工作。并记住确保您的子视图的autoResizeMask
属性设置为 63(即“全部”),以便它们在动画中正确缩放。
:-Joe
:-乔
回答by яοвοτа?τ?яа??
Found this to be the best solution for me. You'll have to use some creativity to do your own customizations
发现这对我来说是最好的解决方案。您必须使用一些创造力来进行自己的自定义
In your MKAnnotationView
subclass, you can use
在您的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++;
}
}
}
}
And if the subview
is a UICalloutView
, then you can screw around with it, and what's inside it.
如果subview
是 a UICalloutView
,那么您可以随意处理它,以及里面有什么。
回答by Sascha Konietzke
I had the same problem. There is a serious of blog posts about this topic on this blog http://spitzkoff.com/craig/?p=81.
我有同样的问题。在这个博客http://spitzkoff.com/craig/?p=81上有很多关于这个主题的博客文章。
Just using the MKMapViewDelegate
doesn't help you here and subclassing MKMapView
and trying to extend the existing functionality also didn't work for me.
仅使用 在MKMapViewDelegate
这里对您没有帮助,子类化MKMapView
并尝试扩展现有功能对我来说也不起作用。
What I ended up doing is to create my own CustomCalloutView
that I am having on top of my MKMapView
. You can style this view in any way you want.
我最终做的是创建我自己的CustomCalloutView
,我在我的MKMapView
. 您可以按您想要的任何方式设置此视图的样式。
My CustomCalloutView
has a method similar to this one:
我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];
}
It takes an MKAnnotation
object and sets its own title, afterward it calls two other methods which are quite ugly which adjust the width and size of the callout contents and afterward draw the speech bubble around it at the correct position.
它接受一个MKAnnotation
对象并设置自己的标题,然后调用另外两个非常丑陋的方法,它们调整标注内容的宽度和大小,然后在正确的位置围绕它绘制对话气泡。
Finally the view is added as a subview to the mapView. The problem with this solution is that it is hard to keep the callout at the correct position when the map view is scrolled. I am just hiding the callout in the map views delegate method on a region change to solve this problem.
最后将视图作为子视图添加到 mapView。此解决方案的问题在于,滚动地图视图时很难将标注保持在正确的位置。我只是在区域更改的地图视图委托方法中隐藏标注以解决此问题。
It took some time to solve all those problems, but now the callout almost behaves like the official one, but I have it in my own style.
解决所有这些问题需要一些时间,但现在标注的行为几乎与官方标注相似,但我有自己的风格。
回答by Shivaraj Tenginakai
Basically to solve this, one needs to: a) Prevent the default callout bubble from coming up. b) Figure out which annotation was clicked.
基本上要解决这个问题,需要: a) 防止出现默认标注气泡。b) 找出点击了哪个注释。
I was able to achieve these by: a) setting canShowCallout to NO b) subclassing, MKPinAnnotationView and overriding the touchesBegan and touchesEnd methods.
我能够通过以下方式实现这些:a) 将 canShowCallout 设置为 NO b) 子类化,MKPinAnnotationView 并覆盖 touchesBegan 和 touchesEnd 方法。
Note: You need to handle the touch events for the MKAnnotationView and not MKMapView
注意:您需要处理 MKAnnotationView 而不是 MKMapView 的触摸事件
回答by Nikesh K
I just come up with an approach, the idea here is
我只是想出了一个方法,这里的想法是
// 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];
}
}
Here - testview is a UIView
of size 320x100 - showCallout is BOOL - [self animateIn];
is the function that does view animation like UIAlertView
.
这里 - testview 是一个UIView
大小为 320x100 的 - showCallout 是 BOOL -[self animateIn];
是像UIAlertView
.
回答by Fabio Napodano
You can use leftCalloutView, setting annotation.text to @" "
您可以使用 leftCalloutView,将 annotation.text 设置为 @" "
Please find below the example code:
请在下面找到示例代码:
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 = @" ";
回答by u10int
I've pushed out my fork of the excellent SMCalloutView that solves the issue with providing a custom view for callouts and allowing flexible widths/heights pretty painlessly. Still some quirks to work out, but it's pretty functional so far:
我已经推出了优秀的 SMCalloutView 的分支,它通过为标注提供自定义视图并允许灵活的宽度/高度非常轻松地解决了这个问题。仍然有一些怪癖需要解决,但到目前为止它非常实用: