用button自定义MKAnnotation标注泡泡
我正在开发应用程序,其中用户通过GPS进行本地化,然后询问他是否位于特定位置。 为了证实这一点,标注泡沫马上提交给他,问他,如果他在特定的地方。
由于有很多类似的问题,我可以做自定义标注泡泡:
我的问题:button不是“可点击”我的猜测:因为这个自定义标注高于标准标注泡泡,我不得不把它放在负面的“框架”,因此button不能被点击。 这是我的didSelectAnnotationView
方法
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view { if(![view.annotation isKindOfClass:[MKUserLocation class]]) { CalloutView *calloutView = (CalloutView *)[[[NSBundle mainBundle] loadNibNamed:@"callOutView" owner:self options:nil] objectAtIndex:0]; CGRect calloutViewFrame = calloutView.frame; calloutViewFrame.origin = CGPointMake(-calloutViewFrame.size.width/2 + 15, -calloutViewFrame.size.height); calloutView.frame = calloutViewFrame; [calloutView.calloutLabel setText:[(MyLocation*)[view annotation] title]]; [calloutView.btnYes addTarget:self action:@selector(checkin) forControlEvents:UIControlEventTouchUpInside]; calloutView.userInteractionEnabled = YES; view.userInteractionEnabled = YES; [view addSubview:calloutView]; } }
CalloutView只是简单的类,有2个属性(显示地名和button名称的标签)和xib。
我一直在做这个定制的宣传泡沫几天。 我尝试使用“asynchronous解决scheme”的解决scheme,但我无法添加任何其他types的button,然后泄露button。
我的下一个尝试是find比asynchronous解决scheme更容易的东西,并将其修改为我的用法。 多数民众赞成我是如何findtochi的自定义标注 。
基于他的工作,我能够自定义他的泡泡和更改我的自定义button的信息button。 然而,我的问题仍然是一样的。 为了将我的自定义标注视图放在引脚上,我必须给它一个负面的框架,所以我的button只能在最底部的5个像素点“可点击”。 看来,我不得不深入到ios的默认标注泡泡,子类化,并在那里改变标注框架。 但是我现在真的没有希望了。
如果你们能以正确的方式给我看,或者给我build议,我会很高兴的。
有几种方法来定制标注:
-
最简单的方法是使用现有的右侧和左侧标注附件,并将button放入其中一个。 例如:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { static NSString *identifier = @"MyAnnotationView"; if ([annotation isKindOfClass:[MKUserLocation class]]) { return nil; } MKPinAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier]; if (view) { view.annotation = annotation; } else { view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier]; view.canShowCallout = true; view.animatesDrop = true; view.rightCalloutAccessoryView = [self yesButton]; } return view; } - (UIButton *)yesButton { UIImage *image = [self yesButtonImage]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(0, 0, image.size.width, image.size.height); // don't use auto layout [button setImage:image forState:UIControlStateNormal]; [button addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventPrimaryActionTriggered]; return button; } - (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control { NSLog(@"%s", __PRETTY_FUNCTION__); }
这产生:
-
如果你真的不喜欢右边的button,那么你可以关掉那个配件,而iOS 9提供了指定
detailCalloutAccessoryView
的机会,它可以用你想要的任何视图replace标注的字幕:- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { static NSString *identifier = @"MyAnnotationView"; if ([annotation isKindOfClass:[MKUserLocation class]]) { return nil; } MKPinAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier]; if (view) { view.annotation = annotation; } else { view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier]; view.canShowCallout = true; view.animatesDrop = true; } view.detailCalloutAccessoryView = [self detailViewForAnnotation:annotation]; return view; } - (UIView *)detailViewForAnnotation:(PlacemarkAnnotation *)annotation { UIView *view = [[UIView alloc] init]; view.translatesAutoresizingMaskIntoConstraints = false; UILabel *label = [[UILabel alloc] init]; label.text = annotation.placemark.name; label.font = [UIFont systemFontOfSize:20]; label.translatesAutoresizingMaskIntoConstraints = false; label.numberOfLines = 0; [view addSubview:label]; UIButton *button = [self yesButton]; [view addSubview:button]; NSDictionary *views = NSDictionaryOfVariableBindings(label, button); [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label]|" options:0 metrics:nil views:views]]; [view addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label]-[button]|" options:0 metrics:nil views:views]]; return view; } - (UIButton *)yesButton { UIImage *image = [self yesButtonImage]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.translatesAutoresizingMaskIntoConstraints = false; // use auto layout in this case [button setImage:image forState:UIControlStateNormal]; [button addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventPrimaryActionTriggered]; return button; }
这产生:
-
如果您确实想自己制作自定义宣传信息,“ 位置和地图编程指南”将概述所涉及的步骤:
在iOS应用程序中,当用户点击标注视图的控件(只要控件是
UIControl
的后代)时,最好使用mapView:annotationView:calloutAccessoryControlTapped:
delegate方法进行响应。 在你实现这个方法的时候,你可以发现标注视图标注视图的标识,以便你知道用户点击了哪个标注。 在Mac应用程序中,标注视图的视图控制器可以实现一个操作方法,当用户在标注视图中单击控件时,该方法会进行响应。当您使用自定义视图而不是标准标注时,您需要做额外的工作,以确保您的标注在用户与之交互时显示并隐藏。 以下步骤概述了创build包含button的自定义标注的过程:
-
devise一个表示自定义标注的
NSView
或UIView
子类。 子类可能需要实现drawRect:
方法来绘制自定义的内容。 -
创build一个视图控制器,初始化标注视图并执行与该button相关的操作。
-
在注释视图中,实现
hitTest:
响应位于注释视图边界之外但位于注解视图边界内的点击,如代码清单6-7所示。 -
在注释视图中,实现
setSelected:animated:
在用户点击或点击时,将标注视图添加为注释视图的子视图。 -
如果标注视图在用户select时已经可见,则
setSelected:
方法应该从注释视图中移除标注子视图(请参见清单6-8)。 -
在注解视图的
initWithAnnotation:
方法中,将canShowCallout
属性设置为NO
以防止在用户select注释时地图显示标准注释。
-
-
前一点概括了一个相当复杂的场景(也就是说,你必须编写自己的代码来检测视图之外的水龙头,以消除它)。 如果您支持iOS 9,则可以使用popup窗口视图控制器,例如:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation { static NSString *identifier = @"MyAnnotationView"; if ([annotation isKindOfClass:[MKUserLocation class]]) { return nil; } MKPinAnnotationView *view = (id)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier]; if (view) { view.annotation = annotation; } else { view = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier]; view.canShowCallout = false; // note, we're not going to use the system callout view.animatesDrop = true; } return view; } - (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view { PopoverController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"AnnotationPopover"]; controller.modalPresentationStyle = UIModalPresentationPopover; controller.popoverPresentationController.sourceView = view; // adjust sourceRect so it's centered over the annotation CGRect sourceRect = CGRectZero; sourceRect.origin.x += [mapView convertCoordinate:view.annotation.coordinate toPointToView:mapView].x - view.frame.origin.x; sourceRect.size.height = view.frame.size.height; controller.popoverPresentationController.sourceRect = sourceRect; controller.annotation = view.annotation; [self presentViewController:controller animated:TRUE completion:nil]; [mapView deselectAnnotation:view.annotation animated:true]; // deselect the annotation so that when we dismiss the popover, the annotation won't still be selected }
我最终采取了不同的方法。 我尝试了其他的,但他们似乎臃肿,我不想添加更多的类或依靠MKMapViewDelegate处理交互。
我反而重写setSelected:我的MKAnnotationView子类的animation。 诀窍就是扩展annotationView的边界,使其在选中之后完全包含调出视图,然后在取消选中后返回正常。 这将允许您的自定义调用接受MKAnnotationView的原始界限以外的触摸和交互。
这是一个修剪下来的代码示例,让任何人开始:
#define kAnnotationCalloutBoxTag 787801 #define kAnnotationCalloutArrowTag 787802 #define kAnnotationTempImageViewTag 787803 -(void)setSelected:(BOOL)selected animated:(BOOL)animated { if (selected == self.selected) { NSLog(@"annotation already selected, abort!"); return; } if (selected) { self.image = nil; //hide default image otherwise it takes the shape of the entire bounds UIView* calloutBox = [self newCustomCallout]; float imgW = [self unselectedSize].width; float imgH = [self unselectedSize].height; float arrowW = 20; float arrowH = 12; //Annotation frames wrap a center coordinate, in this instance we want the call out box to fall under the //central coordinate, so we need to adjust the height to be double what the callout box height would be //making the height *2, this is to make sure the callout view is inside of it. self.bounds = CGRectMake(0, 0, calloutBox.frame.size.width, calloutBox.frame.size.height*2 + arrowH*2 + imgH); CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); UIView* imgView = [[UIImageView alloc] initWithImage:icon]; [imgView setFrame:CGRectMake(center.x - imgW/2, center.y-imgH/2, imgW, imgH)]; imgView.tag = kAnnotationTempImageViewTag; [self addSubview:imgView]; UIView* triangle = [self newTriangleViewWithFrame:CGRectMake(center.x-arrowW/2, center.y+imgH/2, arrowW, arrowH)]; triangle.tag = kAnnotationCalloutArrowTag; [self addSubview:triangle]; [calloutBox setFrame:CGRectMake(0, center.y+imgH/2+arrowH, calloutBox.width, calloutBox.height)]; calloutBox.tag = kAnnotationCalloutBoxTag; [self addSubview:calloutBox]; } else { //return things back to normal UIView* v = [self viewWithTag:kAnnotationCalloutBoxTag]; [v removeFromSuperview]; v = [self viewWithTag:kAnnotationCalloutArrowTag]; [v removeFromSuperview]; v = [self viewWithTag:kAnnotationTempImageViewTag]; [v removeFromSuperview]; self.image = icon; self.bounds = CGRectMake(0, 0, [self unselectedSize].width, [self unselectedSize].height); } [super setSelected:selected animated:animated]; } -(CGSize)unselectedSize { return CGSizeMake(20,20); } -(UIView*)newCustomCallout { //create your own custom call out view UIView* v = [[UIView alloc] initWithFrame:CGRectMake(0,0,250,250)]; v.backgroundColor = [UIColor greenColor]; return v; } -(UIView*)newTriangleWithFrame:(CGRect)frame { //create your own triangle UIImageView* v = [[UIImageView alloc] initWithFrame:frame]; [v setImage:[UIImage imageNamed:@"trianglePointedUp.png"]]; return v; }
(void)mapView:(MKMapView *)mapViewIn didSelectAnnotationView:(MKAnnotationView *)view { if(![view.annotation isKindOfClass:[MKUserLocation class]]) { CustomeCalloutViewController *calloutView = [[CustomeCalloutViewController alloc]initWithNibName:@"CustomeCalloutViewController" bundle:nil]; [calloutView setPopinTransitionStyle:BKTPopinTransitionStyleSlide]; [calloutView setPopinTransitionDirection:BKTPopinTransitionDirectionTop]; [self presentPopinController:calloutView animated:YES completion:^{ NSLog(@"Popin presented !"); }]; [mapView deselectAnnotation:view.annotation animated:true]; } }