APesate APesate - 5 months ago 75
Swift Question

Properly subclassing MKOverlayRenderer

I'm trying to modify the path of a MKPolyline at runtime to avoid it overlaps with another one.

I already managed to get all the overlapping points and what I'm trying to do is in the

func createPath()
of the MKPolylineRenderer add an offset to does points so, theoretically, it should draw the same path with the little offset I'm adding and it shouldn't overlap anymore, but sadly, this is not happening and the Polyline is drawn in the same way like nothing changed.

I first tried to do this after the
addPolyline()
function but I read that once you do that, the one way to redraw a Polyline is by removing it and adding it again, so I decided, for testing purposes, to do all of this before adding the Polyline so when I finally add it to the map, it will already have the information about the overlapping points, but this didn't worked either.

Hypothesis:

1. It has something to do that the map works on different threads and the changes are not reflected because of that. This is ok. It should be this way to optimise the rendering.

2. The correct way to accomplish this is not in the
createPath()
function.
Indeed it isn't


  1. I should apply a transform in the
    draw()
    function of the renderer. This is it



This is the createPath() function

override func createPath()
{
let poly = polyline as! TransportPolyline

switch poly.id
{
case 1:
let newPath = CGMutablePath()

for index in 0...poly.pointCount
{
let point = poly.points()[index]
let predicate = { MKMapPointEqualToPoint($0, poly.points()[index]) }
//This is the offset I should apply
let offset: CGFloat = overlapsAtPoints.contains(predicate) ? 100000.0 : 0.0
//I tried to use a transform as well, but the result was the same
var transform = CGAffineTransform(translationX: offset, y: offset)

if index == 0
{
//Here I add the offset and/or the transform without success
newPath.moveTo(&transform, x: CGFloat(point.x) + offset, y: CGFloat(point.y) + offset)
}
else
{
//Here as well
newPath.addLineTo(&transform, x: CGFloat(point.x) + offset, y: CGFloat(point.y) + offset)
}
}

//Set the new path to the Renderer path property
self.path = newPath
default: break
}
}



And this is the draw() function

override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext)
{
let poly = polyline as! TransportPolyline

guard poly.id == 1 else {
super.draw(mapRect, zoomScale: zoomScale, in: context)
return
}

//If I apply this the Polyline does move, obviously it move all the Path and not only the segments I want.
context.translate(x: 1000, y: 1000)

super.draw(mapRect, zoomScale: zoomScale, in: context)
}


Any suggestions are much appreciated.

UPDATE:

I found out that the problem might be in how I'm drawing the context in the draw method.

The documentation says:


The default implementation of this method does nothing. Subclasses are
expected to override this method and use it to draw the overlay’s
contents.


so by calling
super.draw()
I'm not doing anything.

Any ideas on how to properly override this method? Also taking into consideration this:


To improve drawing performance, the map view may divide your overlay
into multiple tiles and render each one on a separate thread. Your
implementation of this method must therefore be capable of safely
running from multiple threads simultaneously. In addition, you should
avoid drawing the entire contents of the overlay each time this method
is called. Instead, always take the mapRect parameter into
consideration and avoid drawing content outside that rectangle.

Answer

So basically I was on the right track but using the wrong tools. The actual way to accomplish this is by overriding the draw() function in you MKPolylineRenderer subclass.

override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext)
{
   //First validate that the Rect you are asked to draw in actually 
     has some content. See last quote above.
   let theMapRect: MKMapRect = self.overlay.boundingMapRect;

   guard (MKMapRectIntersectsRect(mapRect, theMapRect)) || self.path != nil else {
        return
   }

   //Do some logic if needed.

   //Create and draw your path
   let path = CGMutablePath()
   path.moveTo(nil, x: self.path.currentPoint.x, y: self.path.currentPoint.y)
   path.addLines(nil, between: remainingPoints, count: remainingPoints.count)

   context.addPath(path)

   //Customise it
   context.setStrokeColor(strokeColor!.cgColor)
   context.setLineWidth((lineWidth + CGFloat(0.0)) / zoomScale)

   //And apply it 
   context.strokePath()
}

By doing this I was able to successfully draw the path I wanted for each overlay without any troubles.