Jeshua Lacock
Jeshua Lacock

Reputation: 6688

MKMultiPolylineRenderer with attributes?

Is there any way to use attributes per polyline with a MKMultiPolylineRenderer?

Or do I have to create a different renderer even if the only difference is line width?

I tried sub-classing MKMultiPolylineRenderer, but I do not see anyway to tie attributes to the CGPath being drawn.

In the code below, draw(mapRect:zoomScale:in:) shows I have 1197 polylines, but strokePath(in:) is called only once per draw call.

If I could somehow tell what path number it was stroking I could bind attributes to the strokePath function. Since draw(mapRect:) only draws one path, I don't see how I can. If strokePath only provided an index I'd be all set.

I guess I could override the draw function entirely (and not call its super function), but I would be rendering all 1197 paths 1197 times since I don't know what path it is rendering per call.

class MyMultiPolylineRenderer: MKMultiPolylineRenderer {
    
    //Even though there are 1197 polylines, this is called once per draw(mapRect:)
    override func strokePath(_ path: CGPath, in context: CGContext) {
        super.strokePath(path, in: context)
        print("strokePath")
    }
    
    override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
        
        //Causes a single call to strokePath
        super.draw(mapRect, zoomScale: zoomScale, in: context)

        var counter = 0
        for poly in self.multiPolyline.polylines {
            counter += 1
        }
        
        //polylines counter: 1197
        print("polylines counter: \(counter)")
    }
}

Upvotes: 2

Views: 189

Answers (1)

Gerd Castan
Gerd Castan

Reputation: 6859

First we need a polyline that has an opinion about its color

class ColoredPolyline: MKPolyline {
    var strokeColor: UIColor?
}

Let's create some test data: a straight blue line from Sagrada Família to Museu Blau and a straight yellow line from Sagrada Família to Museu Maritim:

func createTestColoredMultiPolyline() -> MKMultiPolyline {
    let sagradaFamília = CLLocationCoordinate2D(latitude: 41.4035944, longitude: 2.1743616)
    let museuBlau = CLLocationCoordinate2D(latitude: 41.41103109073135, longitude: 2.221040725708008)
    let museuMaritim = CLLocationCoordinate2D(latitude: 41.37546408659406, longitude: 2.1759045124053955)
    
    let toBlauCoordinates = [sagradaFamília, museuBlau]
    let polylineToMuseuBlau = ColoredPolyline(coordinates: toBlauCoordinates, count: toBlauCoordinates.count)
    polylineToMuseuBlau.strokeColor = UIColor.blue
    
    let toMaritimCoordinates = [sagradaFamília, museuMaritim]
    let polylineToMuseuMaritim = ColoredPolyline(coordinates: toMaritimCoordinates, count: toMaritimCoordinates.count)
    polylineToMuseuMaritim.strokeColor = UIColor.yellow
    
    let testMultiPolyline = MKMultiPolyline([polylineToMuseuBlau, polylineToMuseuMaritim])
    
    return testMultiPolyline
}

Note that the sequence of those polylines matters, should you draw them on top of each other.

now let's write a MKMultiPolylineRenderer that respects the color of ColoredPolyline (and uses color of MKMultiPolylineRenderer in other cases):

class MultiColorMultipolylinerenderer: MKMultiPolylineRenderer {
    
    override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
        let roadWidth = MKRoadWidthAtZoomScale(zoomScale)
        
        /// if polylines draw on top of each other the creator of self.multiPolyline is responsible for the correct sequence
        let polylines = self.multiPolyline.polylines
        
        context.saveGState()

        for polyline in polylines {
            // get coordinates of polyline
            let polylineMapPoints = polyline.coordinates.map { coord in
                MKMapPoint(coord)
            }
            guard polylineMapPoints.count > 0 else {
                continue
            }

            let coloredPolyline = polyline as? ColoredPolyline
            // usually all polylines in a MultiColorMultipolylinerenderer are rendered with the same self.strokeColor
            // but we let ColoredPolyline have its own opinion
            let polylineStrokeColor = coloredPolyline?.strokeColor ?? self.strokeColor ?? UIColor.label
            

            // stroke properties to context
            context.setBlendMode(CGBlendMode.exclusion)
            context.setStrokeColor(polylineStrokeColor.cgColor)

            if self.lineWidth > 0 {
                context.setLineWidth(self.lineWidth*roadWidth)
            } else {
                context.setLineWidth(roadWidth)
            }
            context.setLineJoin(self.lineJoin)
            context.setLineCap(self.lineCap)
            context.setMiterLimit(self.miterLimit)
            
            // create path
            context.move(to: self.point(for: polylineMapPoints[0]))
            for element in 1 ..< polyline.pointCount {
                let point_ = self.point(for: polylineMapPoints[element])
                context.addLine(to: point_)
            }
            
            // draw previously created path
            context.drawPath(using: .stroke)
            
        }
        
        context.restoreGState()

    }
    
}

that code uses a little helper:

public extension MKMultiPoint {
    var coordinates: [CLLocationCoordinate2D] {
        var coords = [CLLocationCoordinate2D](repeating: kCLLocationCoordinate2DInvalid, count: pointCount)
        getCoordinates(&coords, range: NSRange(location: 0, length: pointCount))
        return coords
    }
}

This is how the test data looks in my tourist app:

enter image description here

Upvotes: 1

Related Questions