MKPointAnnotation 的自定义视图

Custom View for MKPointAnnotation

我希望在地图上的特定点(折线的起点和终点)上方显示自定义视图。它看起来像这样: enter image description here

视图应该与加载时完全一样,无需点击屏幕。

你应该使用 MKMarkerAnnotationView,我发现这篇文章很有帮助:http://infinityjames.com/blog/mapkit-ios11

它主要是关于聚类,但也展示了如何使用您自己的图形创建一个点。

基本思路是你子class MKAnnotationView 并设置它的image.

您可以任意方式实现注释视图下方的按钮,但请确保实现 hitTest(_:with:) 以便正确识别点击。

例如

class CustomAnnotationView: MKAnnotationView {

    static let lineWidth: CGFloat = 2
    static let pinSize = CGSize(width: 30, height: 37)
    static let pinImage: UIImage = image(with: .blue)

    private let button: UIButton = DisclosureIndicatorButton()

    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

        image = Self.pinImage
        centerOffset = CGPoint(x: 0, y: -Self.pinSize.height / 2)

        configure(for: annotation)
        configureButton()
    }
        
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override var annotation: MKAnnotation? {
        didSet {
            configure(for: annotation)
        }
    }
    
    // this is required for a tap on the callout/button below the annotation view to be recognized

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard let hitView = super.hitTest(point, with: event) else {
            let pointInButton = convert(point, to: button)
            return button.hitTest(pointInButton, with: event)
        }

        return hitView
    }
}

private extension CustomAnnotationView {
    func configure(for annotation: MKAnnotation?) {
        canShowCallout = false
        button.setTitle(annotation?.title ?? "Unknown", for: .normal)

        // if you were also doing clustering, you do that configuration here ...
    }

    func configureButton() {
        addSubview(button)

        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: centerXAnchor),
            button.topAnchor.constraint(equalTo: bottomAnchor, constant: 20)
        ])

        button.addTarget(self, action: #selector(didTapButton(_:)), for: .touchUpInside)
    }

    /// Function to create pin image of the desired color
    ///
    /// You could obviously just create an pre-rendered image in your asset collection,
    /// but I find it just as easy to render them programmatically.

    static func image(with color: UIColor) -> UIImage {
        UIGraphicsImageRenderer(size: pinSize).image { _ in
            let bounds = CGRect(origin: .zero, size: pinSize)
            let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
            let r = rect.width / 2
            let h = rect.height - r
            let theta = acos(r / h)
            let center = CGPoint(x: rect.midX, y: rect.midX)
            let path = UIBezierPath(arcCenter: center, radius: r, startAngle: .pi / 2 - theta, endAngle: .pi / 2 + theta, clockwise: false)
            path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
            path.lineWidth = lineWidth
            path.close()

            color.setFill()
            path.fill()
            UIColor.white.setStroke()
            path.stroke()

            let path2 = UIBezierPath(arcCenter: center, radius: r / 3, startAngle: 0, endAngle: .pi * 2, clockwise: true)
            UIColor.white.setFill()
            path2.fill()
        }
    }

    /// Button handler
    ///
    /// Note, taps on the button are passed to map view delegate via
    /// mapView(_:annotationView:calloutAccessoryControlTapped)`.
    ///
    /// Obviously, you could use your own delegate protocol if you wanted.

    @objc func didTapButton(_ sender: Any) {
        if let mapView = mapView, let delegate = mapView.delegate {
            delegate.mapView?(mapView, annotationView: self, calloutAccessoryControlTapped: button)
        }
    }

    /// Map view
    ///
    /// Navigate up view hierarchy until we find `MKMapView`.

    var mapView: MKMapView? {
        var view = superview
        while view != nil {
            if let mapView = view as? MKMapView { return mapView }
            view = view?.superview
        }
        return nil
    }
}

这导致:

注释视图下方按钮的创建超出了问题的范围,可以像 How do I put the image on the right side of the text in a UIButton? 中讨论的答案之一那样创建在上面的示例中,我刚刚包装了其中一个这些解决方案都有自己的 class、DisclosureIndicatorButton,但我真的不想卷入关于首选方法的争论,所以我会让你选择你想要的任何一个。