Reputation: 6688
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
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:
Upvotes: 1