1. The rendering

2. Implementation idea

A. Initial idea

The initial idea is to add an arrow View or imageView below the point on the MKMapView, calculate the Angle by two points and control the rotation of the arrow to achieve line coincidence, after a operation found that the Angle calculation is not particularly accurate, and when the MKMapView rotates, the arrow also rotates, and can not coincide with the line. Finally, I gave up the idea.

B. the new train of thought

By defining a subclass of MKPolylineRenderer, PVMTPolylineRenderer, Overwrite the parent class – (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context method to draw arrows.

3. Specific code implementation

A. createPVMTPolylineRenderer

#import <MapKit/MapKit.h> NS_ASSUME_NONNULL_BEGIN @interface PVMTPolylineRenderer : MKPolylineRenderer @property (nonatomic, assign) double zoomScale; @end NS_ASSUME_NONNULL_ENDCopy the code
#import "PVMTPolylineRenderer.h" @implementation PVMTPolylineRenderer - (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context { [super drawMapRect:mapRect zoomScale:zoomScale inContext:context]; Double our lineWidth = 1.5 / self. ZoomScale; // Double pointWidth = 27/self.zoomScale; // Double arrowWidth = 9.5/self.zoomScale; @autoreleasepool {for (int I = 0; i < self.polyline.pointCount; i++) { CGPoint currentPoint = [self pointForMapPoint:self.polyline.points[i]]; if (i > 0) { CGPoint lastPoint = [self pointForMapPoint:self.polyline.points[i - 1]]; double angle = [self getPointAngleWithLastPoint:lastPoint currentPoint:currentPoint]; // Calculate the coordinates of arrow 7 points, The starting point is the arrow pointed CGPoint p1 = [self calcCircleCoordinateWithCenter: currentPoint withAngle: Angle withRadius: pointWidth / 2.0]; CGPoint p2 = CGPointMake(p1.x + arrowWidth * cos([self getRadio:30 + angle]), p1.y - arrowWidth * sin([self getRadio:30 + angle])); CGPoint p3 = CGPointMake(p2.x + (arrowWidth/2 - lineWidth/2)*sin([self getRadio:angle]), p2.y + (arrowWidth/2 - lineWidth/2)*cos([self getRadio:angle])); CGPoint p4 = CGPointMake(lastPoint.x - lineWidth/2 * sin([self getRadio:angle]), lastPoint.y - lineWidth/2 * cos([self getRadio:angle])); CGPoint p5 = CGPointMake(lastPoint.x + lineWidth/2 * sin([self getRadio:angle]), lastPoint.y + lineWidth/2 * cos([self getRadio:angle])); CGPoint p7 = CGPointMake(p1.x + arrowWidth * cos([self getRadio:30 - angle]), p1.y + arrowWidth * sin([self getRadio:30 - angle])); CGPoint p6 = CGPointMake(p7.x - (arrowWidth/2 - lineWidth/2)*sin([self getRadio:angle]), p7.y - (arrowWidth/2 - lineWidth/2)*cos([self getRadio:angle])); // Start drawing CGContextMoveToPoint(context, p1.x, p1.y); CGContextAddLineToPoint(context, p2.x, p2.y); CGContextAddLineToPoint(context, p3.x, p3.y); CGContextAddLineToPoint(context, p4.x, p4.y); CGContextAddLineToPoint(context, p5.x, p5.y); CGContextAddLineToPoint(context, p6.x, p6.y); CGContextAddLineToPoint(context, p7.x, p7.y); CGContextClosePath(context); // Fill with the color CGContextSetRGBFillColor(context, 53/255.0, 70/255.0, 222/255.0, 1); CGContextFillPath(context); }}}} /// calculate the Angle between the two points /// @param lastPoint lastPoint /// @param currentPoint currentPoint - (double)getPointAngleWithLastPoint:(CGPoint)lastPoint currentPoint:(CGPoint)currentPoint { CGFloat bearing = M_PI - atan2(currentPoint.y - lastPoint.y, currentPoint.x - lastPoint.x); CGFloat angle = bearing/M_PI * 180; return angle; // @param center // @param Angle // @param radius - (CGPoint)calcCircleCoordinateWithCenter:(CGPoint)center withAngle:(double)angle withRadius:(double)radius { double x2 = radius*cos([self getRadio:angle]); double y2 = radius*sin([self getRadio:angle]); return CGPointMake(center.x+x2, center.y-y2); } @param - (double)getRadio:(double) Angle {return Angle * M_PI / 180; }Copy the code

b. PVMTPolylineRendererThe use of

// PVMKMapView.h @property (nonatomic, strong) PVMTPolylineRenderer *polylineRenderer; // PVMKMapView.m #pragma mark - MKMapViewDelegate - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay { if ([overlay isKindOfClass:[PVMTRouteMKPloyline class]]) { PVMTPolylineRenderer *polylineRenderer = [[PVMTPolylineRenderer alloc] initWithOverlay:overlay]; self.polylineRenderer = polylineRenderer; return polylineRenderer; } if (overlay isKindOfClass:[overlay isKindOfClass]]) {overlay isKindOfClass:[overlay isKindOfClass]]) {overlay isKindOfClass:[overlay isKindOfClass]]} } - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {if (@available(iOS 11.0, *)) {/ / mapViewDidChangeVisibleRegion treatment} else {the if (self. Frame. The size. Width > 300) {double zoomScale = mapView.bounds.size.width/mapView.visibleMapRect.size.width; self.polylineRenderer.zoomScale = zoomScale; [self.polylineRenderer setNeedsDisplay]; } } } - (void)mapViewDidChangeVisibleRegion:(MKMapView *)mapView { if (self.frame.size.width > 300) { double zoomScale =  mapView.bounds.size.width/mapView.visibleMapRect.size.width; self.polylineRenderer.zoomScale = zoomScale; [self.polylineRenderer setNeedsDisplay]; }}Copy the code

Draw the arrow line at this point.

📢 note:

ZoomScale for manual calculation, through the mapView. The bounds. The size, width/mapView visibleMapRect. Size. The calculated width, cannot use the zoomScale drawMapRect method.

ZoomScale using drawMapRect will result in incorrect calculation of arrow coordinates when zooming the map with two fingers.