如何为 Mapbox 注释创建自定义标注?

How do you create a Custom Callout for Mapbox annotations?

我已经试了几个小时了。 Mapbox网站上的资料就是这样显示的:

func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
// Instantiate and return our custom callout view.
return CustomCalloutView(representedObject: annotation)
}

问题是没有详细说明 'CustomCalloutView' 是什么或包含什么以实现 CustomCallout。我理解(我认为)它是一个实现 MGLCalloutView 的 class 但是创建一个正确实现该方法的 class 并不是一件容易的事,我遇到了各种各样的错误,特别是围绕一个函数 'self' -> 自己。

很高兴看到有关如何实际实施自定义标注的示例。 Mapbox 上的所有对话 Git 对于像我这样的傻瓜来说太复杂了。

MGLAnnotation是一个NSObjectProtocol,只需要实现它的classes and/or对象有一个CLLocationCoordinate2D。该对象应该是您的数据模型或与其密切相关。为简单起见,我继承自 NSObject。

CustomAnnotation.swift

import Foundation
import UIKit
import Mapbox

class CustomAnnotation: NSObject, MGLAnnotation {

    var coordinate: CLLocationCoordinate2D
    var title: String?
    var subtitle: String?
    var image: UIImage

    init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String, image: UIImage) {
        self.coordinate = coordinate
        self.title = title
        self.subtitle = subtitle
        self.image = image
    }
}

您的自定义标注视图 (MGLCalloutView) 是另一个协议,任何 class 或从 NSObject 继承的对象都可以符合并具有以下必需属性,请注意我是使用继承自 NSObject 的 UIView 进行子classing:

class CustomCallOutView: UIView, MGLCalloutView {

    var representedObject: MGLAnnotation
    // Required views but unused for now, they can just relax
    lazy var leftAccessoryView = UIView()
    lazy var rightAccessoryView = UIView()

    var delegate: MGLCalloutViewDelegate?

    required init(annotation: MGLAnnotation) {
        self.representedObject = annotation
        super.init()
    }

    func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {

    }

    func dismissCallout(animated: Bool) {

    }
}

注意 require init(annotation:) 有点误导,因为人们认为 annotation 是一个对象,而不是一个符合 MGLAnnotation 的对象,所以我们可以改变这个到我们自己的 MGLAnnotation 数据模型版本。

required init(annotation: CustomAnnotation) {
    self.representedObject = annotation
    super.init()
}

现在,在 MGLCalloutViewDelegate 委托方法 presentCallout(rect:view:constrainedRect:) 中,我们将自定义标注(self)添加到作为视图传递到委托函数中的 mapView。我们还想在 dismissed 的时候将 view 从 super view 中移除:

func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
    view.addSubview(self)

}

func dismissCallout(animated: Bool) {
    if (animated){
        //do something cool
        removeFromSuperview()
    } else {
        removeFromSuperview()
    }
}

最后,在您的 mapView(_: calloutViewFor annotation:) 方法中,从您的 class 或符合 MGLAnnotation 的对象创建一个新的自定义注释,并将其传递给您的自定义标注视图:

func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {

    let title = annotation.title ?? nil
    let subtitle = annotation.subtitle ?? nil
    let image = UIImage(named: "apple.png")!
    let customAnnotation = CustomAnnotation(coordinate: annotation.coordinate, title: title ?? "no title", subtitle: subtitle ?? "no subtitle", image: image)

    return CustomCalloutView(annotation: customAnnotation)
}

这里是我完整实现的其余部分供参考:

CustomAnnotation.swift

见上文

ViewController.swift

import UIKit
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {

    lazy var mapView: MGLMapView = {
        let mv = MGLMapView(frame: self.view.bounds, styleURL: URL(string: "mapbox://styles/mapbox/streets-v10"))
        mv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mv.setCenter(CLLocationCoordinate2D(latitude: 40.7326808, longitude: -73.9843407), zoomLevel: 9, animated: false)
        return mv
    }()


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        setup()

        // Declare the marker `hello` and set its coordinates, title, and subtitle.
        let hello = MGLPointAnnotation()
        hello.coordinate = CLLocationCoordinate2D(latitude: 40.7326808, longitude: -73.9843407)
        hello.title = "Hello world!"
        hello.subtitle = "Welcome to my marker"

        // Add marker `hello` to the map.
        mapView.addAnnotation(hello)
    }

    func setup() {
        self.view.addSubview(mapView)
        mapView.delegate = self
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    // Use the default marker. See also: our view annotation or custom marker examples.
    func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
        return nil
    }

    // Allow callout view to appear when an annotation is tapped.
    func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
        return true
    }


    func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {

        let title = annotation.title ?? nil
        let subtitle = annotation.subtitle ?? nil
        let image = UIImage(named: "apple.png")!
        let customAnnotation = CustomAnnotation(coordinate: annotation.coordinate, title: title ?? "no title", subtitle: subtitle ?? "no subtitle", image: image)

        return CustomCalloutView(annotation: customAnnotation)
    }
}

CustomCalloutView

import Foundation
import Mapbox

class CustomCalloutView: UIView, MGLCalloutView {

    var representedObject: MGLAnnotation
    // Required views but unused for now, they can just relax
    lazy var leftAccessoryView = UIView()
    lazy var rightAccessoryView = UIView()

    weak var delegate: MGLCalloutViewDelegate?

    //MARK: Subviews -
    let titleLabel:UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.boldSystemFont(ofSize: 17.0)
        return label
    }()

    let subtitleLabel:UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    let imageView:UIImageView = {
        let imageview = UIImageView(frame: CGRect(x: 0, y: 0, width: 25, height: 25))
        imageview.translatesAutoresizingMaskIntoConstraints = false
        imageview.contentMode = .scaleAspectFit
        return imageview
    }()

    required init(annotation: CustomAnnotation) {
        self.representedObject = annotation
        // init with 75% of width and 120px tall
        super.init(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: UIScreen.main.bounds.width * 0.75, height: 120.0)))

        self.titleLabel.text = self.representedObject.title ?? ""
        self.subtitleLabel.text = self.representedObject.subtitle ?? ""
        self.imageView.image = annotation.image
        setup()
    }

    required init?(coder decoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setup() {
        // setup this view's properties
        self.backgroundColor = UIColor.white

        // And their Subviews
        self.addSubview(titleLabel)
        self.addSubview(subtitleLabel)
        self.addSubview(imageView)

        // Add Constraints to subviews
        let spacing:CGFloat = 8.0

        imageView.topAnchor.constraint(equalTo: self.topAnchor, constant: spacing).isActive = true
        imageView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: spacing).isActive = true
        imageView.heightAnchor.constraint(equalToConstant: 52.0).isActive = true
        imageView.widthAnchor.constraint(equalToConstant: 52.0).isActive = true

        titleLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: spacing).isActive = true
        titleLabel.leftAnchor.constraint(equalTo: self.imageView.rightAnchor, constant: spacing * 2).isActive = true
        titleLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -spacing).isActive = true
        titleLabel.heightAnchor.constraint(equalToConstant: 50.0).isActive = true

        subtitleLabel.topAnchor.constraint(equalTo: self.titleLabel.bottomAnchor, constant: spacing).isActive = true
        subtitleLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: spacing).isActive = true
        subtitleLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -spacing).isActive = true
        subtitleLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
    }


    func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedRect: CGRect, animated: Bool) {
        //Always, Slightly above center
        self.center = view.center.applying(CGAffineTransform(translationX: 0, y: -self.frame.height))
        view.addSubview(self)

    }

    func dismissCallout(animated: Bool) {
        if (animated){
            //do something cool
            removeFromSuperview()
        } else {
            removeFromSuperview()
        }

    }
}