自定义MKAnnotation标注带按钮的气泡 [英] Custom MKAnnotation callout bubble with button

查看:141
本文介绍了自定义MKAnnotation标注带按钮的气泡的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发app,用户通过gps进行本地化,然后询问他是否位于特定位置。为了证实这一点,他会立即向他提出标注泡沫,询问他是否在特定的地方。



由于有很多类似的问题,我能够做自定义标注泡泡:



我的问题:按钮不是可点击
我的猜测:因为这个自定义标注高于标准的标注气泡,我不得不把它放在负框架中,因此按钮不能被点击。这是我的 didSelectAnnotationView 方法

   - (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
if(![view.annotation isKindOfClass:[MKUserLocation class]]){
CalloutView * calloutView =(CalloutView *)[[[NSBundle mainBundle] loadNibNamed :@callOutView所有者:self选项: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;
[查看addSubview:calloutView];
}

}

CalloutView只是简单的2级属性(显示地点名称和按钮的标签)和xib。



我一直在做这个自定义标注泡泡几天。我尝试使用异步解决方案


  • 如果你真的不喜欢右边的按钮,配件一般都去了,你可以关掉那个配件,iOS 9提供了指定 detailCalloutAccessoryView 的机会,它用你想要的任何视图替换了标注的副标题:

       - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id< MKAnnotation>)注释{
    static NSString * identifier = @MyAnnotationView ;

    if([注释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];

    返回视图;
    }

    - (UIView *)detailViewForAnnotation :( PlacemarkAnnotation *)注释{
    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;
    [查看addSubview:label];

    UIButton * button = [self yesButton];
    [查看addSubview:button];

    NSDictionary * views = NSDictionaryOfVariableBindings(label,button);

    [查看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]];
    [查看addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@V:| [label] - [button] | options:0 metrics:nil views:views]];

    返回视图;
    }

    - (UIButton *)yesButton {
    UIImage * image = [self yesButtonImage];

    UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.translatesAutoresizingMaskIntoConstraints = false; //在这种情况下使用自动布局
    [button setImage:image forState:UIControlStateNormal];
    [button addTarget:self action:@selector(didTapButton :) forControlEvents:UIControlEventPrimaryActionTriggered];

    返回按钮;
    }

    此收益率:




  • 如果您真的想自己开发自定义标注,位置和地图编程指南概述了所涉及的步骤:


    在iOS应用中,最好使用 mapView :annotationView:calloutAccessoryControlTapped:委托方法,当用户点击一个callout视图的控件时响应(只要该控件是 UIControl 的后代)。在此方法的实现中,您可以发现标注视图的注释视图的标识,以便您知道用户点击了哪个注释。在Mac应用程序中,callout视图的视图控制器可以实现一个操作方法,当用户在callout视图中单击该控件时,该方法会响应。



    当您使用自定义视图时对于标准标注,您需要做额外的工作以确保您的标注在用户与其互动时显示和隐藏。以下步骤概述了创建包含按钮的自定义标注的过程:




    • 设计 NSView UIView 表示自定义标注的子类。子类可能需要实现 drawRect:方法来绘制自定义内容。


    • 创建一个视图控制器,用于初始化标注视图并执行与按钮相关的操作。


    • 在注释视图中,实现 hitTest:响应注释视图边界之外但在callout视图边界内的命中,如清单6-7所示。


    • 在注释视图中,实现 setSelected:animated:,以便在用户单击或点击注释视图时将其作为注释视图的子视图添加。


    • 如果用户选择了标注视图已经可见,则应删除 setSelected:方法注释视图中的callout子视图(参见清单6-8)。


    • 在注释视图中的 initWithAnnotation:方法,将 canShowCallout 属性设置为,以防止当用户选择时,地图显示标准标注注释。



    当它在Swift中时, https://github.com/robertmryan/CustomMapViewAnnotationCalloutSwift 说明了如何完成标注的完整自定义示例(例如,更改标注的形状)泡沫,改变背景颜色等。)


  • 前面的一点概述了一个相当复杂的场景(即你必须编写自己的代码来检测外部的点击v iew是为了解雇它)。如果你支持iOS 9,你可能只使用一个弹出视图控制器,例如:

       - (MKAnnotationView *)mapView: (MKMapView *)mapView viewForAnnotation:(id< MKAnnotation>)注释{
    static NSString * identifier = @MyAnnotationView;

    if([注释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; //注意,我们不打算使用系统标注
    view.animatesDrop = true;
    }

    返回视图;
    }

    - (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
    PopoverController * controller = [self.storyboard instantiateViewControllerWithIdentifier:@AnnotationPopover ]。
    controller.modalPresentationStyle = UIModalPresentationPopover;

    controller.popoverPresentationController.sourceView = view;

    //调整sourceRect,使其以注释为中心

    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]; //取消选择注释,以便在我们解除弹出窗口时,仍然不会选择注释
    }



  • I'm developing app, where user is localized by gps and then he is asked, whether he is located in specific place. To confirm this, callout bubble is presented to him straight away, asking him, if he is at specific place.

    As there is alot of similar questions, I was able to do custom callout bubble:

    My problem: button is not "clickable" My guess: because this custom callout is higher than standard callout bubble, I had to place it in negative "frame", therefore button cannot be clicked. Here is my didSelectAnnotationView method

    - (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 is just simple class with 2 properties(label that shows name of place and button) and with xib.

    I have been doing this custom callout bubble for a few days. I tried using "asynchrony solutions" solution but I was unable to add any other kind of button then disclosure button.

    My next attempt, was to find something that was easier than asynchrony solutions and modify it to my use. Thats how I found tochi's custom callout.

    Based on his work, I was able to customize his bubble and change info button for my custom button. My problem however remained the same. In order to place my custom callout view on top of the pin, I had to give it negative frame, so my button was "clickable" only in bottom 5 pixels. It seems, that I have to maybe dig deeper into ios default callout bubble, subclass it and change frame of callout in there. But I'm really hopeless now.

    If you guys could show me the right way, or give me advice, I'll be glad.

    解决方案

    There are several approaches to customizing callouts:

    1. The easiest approach is to use the existing right and left callout accessories, and put your button in one of those. For example:

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

      That yields:

    2. If you really don't like the button on the right, where accessories generally go, you can turn off that accessory, and iOS 9 offers the opportunity to specify the detailCalloutAccessoryView, which replaces the callout's subtitle with whatever view you want:

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

      This yields:

    3. If you really want to develop a custom callout yourself, the Location and Maps Programming Guide outlines the steps involved:

      In an iOS app, it’s good practice to use the mapView:annotationView:calloutAccessoryControlTapped: delegate method to respond when users tap a callout view’s control (as long as the control is a descendant of UIControl). In your implementation of this method you can discover the identity of the callout view’s annotation view so that you know which annotation the user tapped. In a Mac app, the callout view’s view controller can implement an action method that responds when a user clicks the control in a callout view.

      When you use a custom view instead of a standard callout, you need to do extra work to make sure your callout shows and hides appropriately when users interact with it. The steps below outline the process for creating a custom callout that contains a button:

      • Design an NSView or UIView subclass that represents the custom callout. It’s likely that the subclass needs to implement the drawRect: method to draw your custom content.

      • Create a view controller that initializes the callout view and performs the action related to the button.

      • In the annotation view, implement hitTest: to respond to hits that are outside the annotation view’s bounds but inside the callout view’s bounds, as shown in Listing 6-7.

      • In the annotation view, implement setSelected:animated: to add your callout view as a subview of the annotation view when the user clicks or taps it.

      • If the callout view is already visible when the user selects it, the setSelected: method should remove the callout subview from the annotation view (see Listing 6-8).

      • In the annotation view’s initWithAnnotation: method, set the canShowCallout property to NO to prevent the map from displaying the standard callout when the user selects the annotation.

      While it's in Swift, https://github.com/robertmryan/CustomMapViewAnnotationCalloutSwift illustrates an example of how you can do this complete customization of the callout (e.g. change shape of callout bubble, change background color, etc.).

    4. That previous point outlines a pretty complicated scenarios (i.e. you have to write your own code to detecting taps outside the view in order to dismiss the it). If you're supporting iOS 9, you might just use a popover view controller, e.g.:

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

    这篇关于自定义MKAnnotation标注带按钮的气泡的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆