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
,但我真的不想卷入关于首选方法的争论,所以我会让你选择你想要的任何一个。
我希望在地图上的特定点(折线的起点和终点)上方显示自定义视图。它看起来像这样: 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
,但我真的不想卷入关于首选方法的争论,所以我会让你选择你想要的任何一个。