【问题标题】:Custom MKAnnotation callout bubble with button带按钮的自定义 MKAnnotation 标注气泡
【发布时间】:2013-07-20 06:35:59
【问题描述】:

我正在开发应用程序,通过 gps 对用户进行本地化,然后询问他是否位于特定位置。为了确认这一点,标注气泡会立即呈现给他,询问他是否在特定位置。

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

我的问题:按钮不是“可点击的” 我的猜测:因为这个自定义标注高于标准标注气泡,我不得不将它放在负“框架”中,因此无法单击按钮。这是我的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 个属性(显示地点和按钮名称的标签)和 xib 的简单类。

我已经做了几天这个自定义标注气泡。我尝试使用“异步解决方案”solution,但我无法添加任何其他类型的按钮,然后是披露按钮。

我的下一个尝试是找到比异步解决方案更容易的东西,并将其修改为我的使用。我就是这样找到tochi's custom callout的。

根据他的工作,我能够为我的自定义按钮自定义他的气泡和更改信息按钮。然而,我的问题仍然是一样的。为了将我的自定义标注视图放在图钉的顶部,我必须给它负帧,所以我的按钮只能在底部 5 个像素中“可点击”。看来,我可能不得不深入挖掘 ios 默认标注气泡,将其子类化并更改其中的标注框架。但我现在真的没希望了。

如果你们能告诉我正确的方法,或者给我建议,我会很高兴的。

【问题讨论】:

    标签: ios mapkit frame callout


    【解决方案1】:
    (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];
        }
    }
    

    【讨论】:

      【解决方案2】:

      有几种自定义标注的方法:

      1. 最简单的方法是使用现有的左右标注配件,并将您的按钮放入其中之一。例如:

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

        产生:

      2. 如果您真的不喜欢右边的按钮,通常是配件所在的位置,您可以关闭该配件,iOS 9 提供了指定detailCalloutAccessoryView 的机会,它会用您的任何视图替换标注的副标题想要:

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

        这会产生:

      3. 如果您真的想自己开发自定义标注,Location and Maps Programming Guide 概述了所涉及的步骤:

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

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

        • 设计一个代表自定义标注的NSViewUIView 子类。子类很可能需要实现drawRect: 方法来绘制您的自定义内容。

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

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

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

        • 如果标注视图在用户选择时已经可见,setSelected: 方法应该从标注视图中移除标注子视图(参见清单 6-8)。

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

        虽然它在 Swift 中,但 https://github.com/robertmryan/CustomMapViewAnnotationCalloutSwift 举例说明了如何对标注进行完全自定义(例如,更改标注气泡的形状、更改背景颜色等)。

      4. 前一点概述了一个非常复杂的场景(即,您必须编写自己的代码来检测视图外的点击以消除它)。如果您支持 iOS 9,则可以只使用弹出视图控制器,例如:

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

      【讨论】:

      • Rob 非常感谢你,你是真正的救星。我很高兴投票并接受您的回答:)
      • 如果我没有在“CustomAnnotation”类中设置标题和副标题,则根本不会调用标注。如何进行?
      • @Satyamsvv 如果将canShowCallout 设置为NO,则对didSelectAnnotationView 的调用将不受title/subtitle 存在与否的影响。
      • 当我尝试这个解决方案时,标注注释可能会出现在地图上其他注释的“下方”
      • @TD2 当您说“此解决方案”时,您是指第一个(我不推荐)还是第二个。在我的测试中,使用第二种方法,标注位于所选注释下方(这通常不是问题,因为标注通常不会与底层注释重叠),但高于其余注释。无论如何,我将第二个示例更改为在标注注释一出现时就实际选择它(从而确保它位于其余部分的前面)并更改了deselectAnnotationView 以检查标注注释何时被取消选择。
      【解决方案3】:

      我最终采取了不同的方法。我尝试了其他的,但它们看起来很臃肿,我不想添加更多的类或依赖 MKMapViewDelegate 来处理交互。

      我改为覆盖我的 MKAnnotationView 子类的 setSelected:animated。诀窍是在 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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-01
        • 2015-03-19
        • 1970-01-01
        • 2010-12-06
        • 1970-01-01
        • 1970-01-01
        • 2015-08-27
        相关资源
        最近更新 更多