MapAmapArrowPolylineRenderer.swift 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. //
  2. // MapAmapArrowPolylineRenderer.swift
  3. // map_amap_ios
  4. //
  5. // Created by 诺诺诺的言 on 2025/7/22.
  6. //
  7. import Foundation
  8. import MapKit
  9. class MapAmapArrowPolylineRenderer: MKPolylineRenderer {
  10. // 箭头属性
  11. var arrowColor: UIColor = .red
  12. var arrowSize: CGFloat = 8
  13. var arrowSpacing: CGFloat = 30
  14. // 边框属性
  15. var borderWidth: CGFloat = 0 {
  16. didSet {
  17. invalidatePath()
  18. }
  19. }
  20. var borderColor: UIColor = .clear {
  21. didSet {
  22. invalidatePath()
  23. }
  24. }
  25. override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
  26. // 1. 绘制边框(如果启用)
  27. if borderWidth > 0 {
  28. context.saveGState()
  29. // 设置边框样式
  30. context.setLineWidth(lineWidth + borderWidth * 2)
  31. context.setStrokeColor(borderColor.cgColor)
  32. context.setLineCap(lineCap)
  33. context.setLineJoin(lineJoin)
  34. // 创建路径并描边
  35. let path = createPath(for: polyline, in: mapRect, zoomScale: zoomScale)
  36. context.addPath(path)
  37. context.strokePath()
  38. context.restoreGState()
  39. }
  40. // 2. 绘制主线条
  41. super.draw(mapRect, zoomScale: zoomScale, in: context)
  42. // 3. 绘制箭头
  43. drawArrows(mapRect: mapRect, zoomScale: zoomScale, in: context)
  44. }
  45. private func createPath(for polyline: MKPolyline, in mapRect: MKMapRect, zoomScale: MKZoomScale) -> CGPath {
  46. let path = CGMutablePath()
  47. let points = polyline.points()
  48. // 添加第一个点
  49. let firstPoint = self.point(for: points[0])
  50. path.move(to: firstPoint)
  51. // 添加剩余点
  52. for i in 1..<polyline.pointCount {
  53. let point = self.point(for: points[i])
  54. path.addLine(to: point)
  55. }
  56. return path
  57. }
  58. private func drawArrows(mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
  59. guard polyline.pointCount >= 2 else { return }
  60. let scaledArrowSize = arrowSize / zoomScale
  61. let scaledArrowSpacing = arrowSpacing / zoomScale
  62. var distance: CGFloat = 0
  63. let points = polyline.points()
  64. for i in 0..<polyline.pointCount-1 {
  65. let startPoint = point(for: points[i])
  66. let endPoint = point(for: points[i+1])
  67. let dx = endPoint.x - startPoint.x
  68. let dy = endPoint.y - startPoint.y
  69. let segmentLength = hypot(dx, dy)
  70. guard segmentLength > 2 / zoomScale else { continue }
  71. let arrowCount = Int(floor((distance + segmentLength) / scaledArrowSpacing))
  72. for j in 0..<arrowCount {
  73. let position = CGFloat(j) * scaledArrowSpacing - distance
  74. let ratio = position / segmentLength
  75. guard ratio >= 0 && ratio <= 1 else { continue }
  76. let arrowPoint = CGPoint(
  77. x: startPoint.x + dx * ratio,
  78. y: startPoint.y + dy * ratio
  79. )
  80. let angle = atan2(dy, dx)
  81. drawSingleArrow(at: arrowPoint, angle: angle, size: scaledArrowSize, in: context)
  82. }
  83. distance = (distance + segmentLength).truncatingRemainder(dividingBy: scaledArrowSpacing)
  84. }
  85. }
  86. private func drawSingleArrow(at point: CGPoint, angle: CGFloat, size: CGFloat, in context: CGContext) {
  87. context.saveGState()
  88. context.translateBy(x: point.x, y: point.y)
  89. context.rotate(by: angle)
  90. let arrowPath = UIBezierPath()
  91. arrowPath.move(to: CGPoint(x: 3.2, y: 0))
  92. arrowPath.addLine(to: CGPoint(x: 0, y: 2.6))
  93. arrowPath.addLine(to: CGPoint(x: -3.2, y: 2.6))
  94. arrowPath.addLine(to: CGPoint(x: 0, y: 0))
  95. arrowPath.addLine(to: CGPoint(x: -3.2, y: -2.6))
  96. arrowPath.addLine(to: CGPoint(x: 0, y: -2.6))
  97. arrowPath.close()
  98. context.setFillColor(arrowColor.cgColor)
  99. context.addPath(arrowPath.cgPath)
  100. context.fillPath()
  101. context.restoreGState()
  102. }
  103. }