如何为 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()
}
}
}
我已经试了几个小时了。 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()
}
}
}