ios8 - 自定义 iOS8 标注气泡 (Swift)

标签 ios8 mkmapview mkannotationview calloutview

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

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

Apple documentation我发现了这个:

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. Listing 6-7 shows an example of implementing hitTest: to handle hits in the callout view that might be outside the bounds of the annotation view.


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

Listing 6-8 shows an example of implementing setSelected:animated: to animate the arrival and dismissal of a custom callout view when the user selects the annotation view.


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;

如何访问标注气泡 View ?

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

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

最佳答案

calloutViewController 是处理事件的自定义标注 View 的一部分。您不会在 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

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

注解

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

标注 View

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 返回 View 之前处理特定 View 中的触摸

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

委托(delegate)方法

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

关于ios8 - 自定义 iOS8 标注气泡 (Swift),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28066623/

相关文章:

ios - Xcode Device Simulator的文档目录路径

ios - TestFlight iOS 8 - 错误 : An invalid app was specified for the install request

ios - showCallout 无需点击 -iOS

xcode - 在 iPad (iOS 8) 上运行 Xcode 6 应用程序时出现 "Installation of apps is prohibited by a policy on the device."错误

Swift 2.0 locationManager 刷新 View ,但一旦 View 加载就不会保持静止

MKMapView 缩放到注释 - annotationVisibleRect

mapkit - 如何在iOS map 中突出显示国家

ios - MKAnnotationView 在 iOS 的 mapview 上不显示图像

ios - Apple 如何在 map View 的标注中添加评级星?

调试时 iOS 8 自定义键盘崩溃