ArrowPolylineRenderer.swift 4.3 KB

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