ArrowPolylineRenderer.swift 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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. var borderColor: UIColor = .clear
  18. override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
  19. // 1. 绘制边框
  20. if borderWidth > 0 {
  21. context.saveGState()
  22. // 边框宽度 = 主线条宽度 + 2倍边框宽度(左右各扩展 borderWidth)
  23. let borderLineWidth = lineWidth + borderWidth * 2
  24. context.setLineWidth(borderLineWidth)
  25. context.setStrokeColor(borderColor.cgColor)
  26. context.setLineCap(lineCap) // 保持与主线条一致的线帽样式
  27. context.setLineJoin(lineJoin) // 保持与主线条一致的拐角样式
  28. // 绘制边框线条
  29. super.draw(mapRect, zoomScale: zoomScale, in: context)
  30. context.restoreGState()
  31. }
  32. // 2. 绘制主轨迹线
  33. super.draw(mapRect, zoomScale: zoomScale, in: context)
  34. // 3. 绘制箭头(数量减半)
  35. drawArrows(mapRect: mapRect, zoomScale: zoomScale, in: context)
  36. }
  37. private func drawArrows(mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
  38. guard polyline.pointCount >= 2 else { return }
  39. let scaledArrowSize = arrowSize / zoomScale
  40. let scaledArrowSpacing = arrowSpacing / zoomScale
  41. var distance: CGFloat = 0
  42. let points = polyline.points()
  43. // 使用stride跳过每隔一个的箭头
  44. let step = 2 // 跳过间隔
  45. var arrowIndex = 0
  46. for i in 0..<polyline.pointCount-1 {
  47. let startPoint = point(for: points[i])
  48. let endPoint = point(for: points[i+1])
  49. let dx = endPoint.x - startPoint.x
  50. let dy = endPoint.y - startPoint.y
  51. let segmentLength = hypot(dx, dy)
  52. guard segmentLength > 2 / zoomScale else { continue }
  53. let arrowCount = Int(floor((distance + segmentLength) / scaledArrowSpacing))
  54. for j in 0..<arrowCount {
  55. // 只绘制每隔step个箭头
  56. if arrowIndex % step == 0 {
  57. let position = CGFloat(j) * scaledArrowSpacing - distance
  58. let ratio = position / segmentLength
  59. guard ratio >= 0 && ratio <= 1 else { continue }
  60. let arrowPoint = CGPoint(
  61. x: startPoint.x + dx * ratio,
  62. y: startPoint.y + dy * ratio
  63. )
  64. let angle = atan2(dy, dx)
  65. drawSingleArrow(at: arrowPoint, angle: angle, size: scaledArrowSize, in: context)
  66. }
  67. arrowIndex += 1
  68. }
  69. distance = (distance + segmentLength).truncatingRemainder(dividingBy: scaledArrowSpacing)
  70. }
  71. }
  72. private func drawSingleArrow(at point: CGPoint, angle: CGFloat, size: CGFloat, in context: CGContext) {
  73. context.saveGState()
  74. context.translateBy(x: point.x, y: point.y)
  75. context.rotate(by: angle)
  76. let arrowPath = UIBezierPath()
  77. arrowPath.move(to: CGPoint(x: 3.2, y: 0))
  78. arrowPath.addLine(to: CGPoint(x: 0, y: 2.6))
  79. arrowPath.addLine(to: CGPoint(x: -3.2, y: 2.6))
  80. arrowPath.addLine(to: CGPoint(x: 0, y: 0))
  81. arrowPath.addLine(to: CGPoint(x: -3.2, y: -2.6))
  82. arrowPath.addLine(to: CGPoint(x: 0, y: -2.6))
  83. arrowPath.close()
  84. context.setFillColor(arrowColor.cgColor)
  85. context.addPath(arrowPath.cgPath)
  86. context.fillPath()
  87. context.restoreGState()
  88. }
  89. }