Как создать пользовательский выносной элемент для аннотаций Mapbox?

Я пытался в течение нескольких часов. Материалы на сайте Mapbox просто показывают это:

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

Проблемы заключаются в том, что нет никакой детализации того, что представляет собой или содержит CustomCalloutView для достижения CustomCallout. Я понимаю (я думаю), что это класс, который реализует MGLCalloutView, но создание класса, который правильно реализует этот метод, не простая задача, я получаю всевозможные ошибки, особенно в отношении одной функции 'self' -> Self.

Было бы здорово увидеть пример того, как на самом деле реализовать Custom Callout. Все разговоры в Mapbox Git слишком сложны для такого простого человека, как я.

1 ответ

Решение

MGLAnnotation это NSObjectProtocolЭто требует только, чтобы классы и / или объект, который его реализует, имели 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) еще один протокол, который наследует любой класс или объект NSObject может соответствовать и имеет следующие обязательные свойства, обратите внимание, что я создаю подклассы с UIView, который наследуется от NSObject:

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:) мы добавляем пользовательский callout (self) в mapView, который передается в функцию делегата как 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:) метод создания новой пользовательской аннотации из вашего класса или объекта, который соответствует 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()
        }

    }
}
Другие вопросы по тегам