13

我想自定义单击 MKAnnotationView 时可视化的 iOS8 MapView 标注气泡。默认气泡有点限制(只有标题、副标题和 2 个附件视图),所以我正在努力寻找替代解决方案。这里有两种可能的方法和我面临的相关问题:

问题 1) 创建自定义标注气泡

挖掘Apple文档我发现了这个:

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

设计一个代表自定义标注的 NSView 或 UIView 子类。子类很可能需要实现 drawRect: 方法来绘制您的自定义内容。创建一个视图控制器,用于初始化标注视图并执行与按钮相关的操作。在注解视图中,实现 hitTest: 以响应在注解视图范围之外但在标注视图范围内的命中,如清单 6-7 所示。在注释视图中,实现 setSelected:animated: 以在用户单击或点击时将标注视图添加为注释视图的子视图。如果用户选择时标注视图已经可见,则 setSelected: 方法应从注释视图中删除标注子视图(参见清单 6-8)。在注解视图的 initWithAnnotation: 方法中,将 canShowCallout 属性设置为 NO 以防止地图在用户选择注释时显示标准标注。清单 6-7 显示了一个实现 hitTest 的示例:处理标注视图中可能超出注释视图边界的命中。

Listing 6-7  Responding to hits within a custom callout
- (NSView *)hitTest:(NSPoint)point
{
    NSView *hitView = [super hitTest:point];
    if (hitView == nil && self.selected) {
        NSPoint pointInAnnotationView = [self.superview convertPoint:point toView:self];
        NSView *calloutView = self.calloutViewController.view;
        hitView = [calloutView hitTest:pointInAnnotationView];
    }
    return hitView;
}

清单 6-8 显示了一个实现 setSelected:animated: 的示例,以在用户选择注释视图时为自定义标注视图的到达和解除设置动画。

Listing 6-8  Adding and removing a custom callout view
- (void)setSelected:(BOOL)selected
{
    [super setSelected:selected];

    // Get the custom callout view.
    NSView *calloutView = self.calloutViewController.view;
    if (selected) {
        NSRect annotationViewBounds = self.bounds;
        NSRect calloutViewFrame = calloutView.frame;
      // Center the callout view above and to the right of the annotation view.
        calloutViewFrame.origin.x = -(NSWidth(calloutViewFrame) - NSWidth(annotationViewBounds)) * 0.5;
        calloutViewFrame.origin.y = -NSHeight(calloutViewFrame) + 15.0;
        calloutView.frame = calloutViewFrame;

        [self addSubview:calloutView];
    } else {
        [calloutView.animator removeFromSuperview];
    }
}

现在,当我尝试将此 Objective-C 代码转换为 Swift 时,我找不到此属性:

NSView *calloutView = self.calloutViewController.view;

如何访问标注气泡视图?

问题 2) 修改默认标注气泡

如前所述,显示的默认标注有标题、副标题和 2 个附件视图。我注意到我无法改变字符串的字体样式或气泡的颜色。此外,如果我的标题超过 24 个字符,我的辅助视图定位就会混乱。我怎样才能避免这个问题?

4

1 回答 1

19

calloutViewController 是处理事件的自定义标注视图的一部分。您不会在 MapKit 或其他地方找到它。
苹果的说明很好。要创建自己的标注,您应该按照以下步骤操作:

1. Create custom MKAnnotationView or MAPinAnnotationView
2. Override setSelected and hitTest methods in your annotation
3. Create your own callout view
4. Override hitTest and pointInside in you callout view
5. Implement MapView delegate methods didSelectAnnotationView, didDeselectAnnotationView

我最终得到了这些解决方案,它允许我在标注视图中处理触摸而不会丢失选择。

注解

class MapPin: MKAnnotationView {
    class var reuseIdentifier:String {
        return "mapPin"
    }

    private var calloutView:MapPinCallout?
    private var hitOutside:Bool = true

    var preventDeselection:Bool {
        return !hitOutside
    }


    convenience init(annotation:MKAnnotation!) {
        self.init(annotation: annotation, reuseIdentifier: MapPin.reuseIdentifier)

        canShowCallout = false;
    }

    override func setSelected(selected: Bool, animated: Bool) {
        let calloutViewAdded = calloutView?.superview != nil

        if (selected || !selected && hitOutside) {
            super.setSelected(selected, animated: animated)
        }

        self.superview?.bringSubviewToFront(self)

        if (calloutView == nil) {
            calloutView = MapPinCallout()
        }

        if (self.selected && !calloutViewAdded) {
            addSubview(calloutView!)
        }

        if (!self.selected) {
            calloutView?.removeFromSuperview()
        }
    }

    override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        var hitView = super.hitTest(point, withEvent: event)

        if let callout = calloutView {
            if (hitView == nil && self.selected) {
                hitView = callout.hitTest(point, withEvent: event)
            }
        }

        hitOutside = hitView == nil

        return hitView;
    }
}

标注视图

class MapPinCallout: UIView {
    override func hitTest(var point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let viewPoint = superview?.convertPoint(point, toView: self) ?? point

        let isInsideView = pointInside(viewPoint, withEvent: event)

        var view = super.hitTest(viewPoint, withEvent: event)

        return view
    }

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        return CGRectContainsPoint(bounds, point)
    }
}

如果您需要其他东西,但按钮在标注中响应,请添加代码以在 hitTest 返回视图之前处理特定视图中的触摸

if calloutState == .Expanded && CGRectContainsPoint(tableView.frame, viewPoint) {
    view = tableView.hitTest(convertPoint(viewPoint, toView: tableView), withEvent: event)
}

委托方法

func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!) {
    if let mapPin = view as? MapPin {
        updatePinPosition(mapPin)
    }
}

func mapView(mapView: MKMapView!, didDeselectAnnotationView view: MKAnnotationView!) {
    if let mapPin = view as? MapPin {
        if mapPin.preventDeselection {
            mapView.selectAnnotation(view.annotation, animated: false)
        }
    }
}

func updatePinPosition(pin:MapPin) {
    let defaultShift:CGFloat = 50
    let pinPosition = CGPointMake(pin.frame.midX, pin.frame.maxY)

    let y = pinPosition.y - defaultShift

    let controlPoint = CGPointMake(pinPosition.x, y)
    let controlPointCoordinate = mapView.convertPoint(controlPoint, toCoordinateFromView: mapView)

    mapView.setCenterCoordinate(controlPointCoordinate, animated: true)
}
于 2015-02-12T21:30:40.853 回答