MapKit : add MKPolyline hide MKTileOverlay

1k Views Asked by At

I have a MKMapView working with a MKTileOverlay showing tiles from a local database. It's working fine.
Then I used MKDirections to get direction between two coordinates and draw the route like that :

MKRoute *route = response.routes.lastObject;
MKPolyline *polyline = route.polyline;

// Draw path on overlay
[self.mapView insertOverlay:polyline aboveOverlay:self.tileOverlay];

But when I zoom to see the line, it appears without the tile background (normaly loaded from MKTileOverlay (stored into self.tileOverlay)). I joined an image to see better.

I also made this code to render overlays :

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    if ([overlay isKindOfClass:[MKTileOverlay class]]) {
        return [[MKTileOverlayRenderer alloc] initWithTileOverlay:overlay];
    }
    else if ([overlay isKindOfClass:[MKPolyline class]]) {
        MKPolylineRenderer *lineView = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
        lineView.strokeColor = [UIColor greenColor];
        lineView.lineWidth = 3;
        return lineView;
    }
    return nil;
}

It's like the "tile" that render the line hide the tile loaded from the MKTileOverlay. How can I :
- specify that I the MKPolyline overlay must be transparent ?
- reload the background tile ?

Screeshot :

See the tile with line has no background anymore http://sigmanet.ch/tmp/screen.png

2

There are 2 best solutions below

1
On

After days of work, here is my own solution.

Extend MKPolylineRenderer and add a reference to the MKTileOverlayRenderer. Let's call this new class MCPolylineRenderer.

In this class, override this two methods :

- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
    // Draw path only if tile render has a tile
    if ([self.tileRenderRef canDrawMapRect:mapRect zoomScale:zoomScale]) {
        [super drawMapRect:mapRect zoomScale:zoomScale inContext:context];
    }
}

- (BOOL)canDrawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale {
    // We can draw path only if tile render can also
    return [self.tileRenderRef canDrawMapRect:mapRect zoomScale:zoomScale];
}

Now, in the mapView:renderedForOverlay method, replace

MKPolylineRenderer *lineView = [[MKPolylineRenderer alloc] initWithOverlay:overlay]; 

with

MCPolylineRenderer *lineView = [[MCPolylineRenderer alloc] initWithPolyline:overlay];
lineView.tileRenderRef = self.tileRender;

Also, you need to be sure that the loadTileAtPath:result: method doesn't result a tile when there is nothing to render (like a "tile not found" image).

This code will have effect that when there is no background tile to render, the path won't be draw neither.

2
On

You'll have to subclass MKPolylineRenderer to synchronize renderers drawing abilities.

import Foundation
import MapKit

class MyPolylineRenderer: MKPolylineRenderer {

    var tileRenderer: MKTileOverlayRenderer?

    override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
        if (tileRenderer?.canDraw(mapRect, zoomScale: zoomScale) ?? true) {
            super.draw(mapRect, zoomScale: zoomScale, in: context)
        }
    }

    override func canDraw(_ mapRect: MKMapRect, zoomScale: MKZoomScale) -> Bool {
        return tileRenderer?.canDraw(mapRect, zoomScale: zoomScale) ?? super.canDraw(mapRect, zoomScale: zoomScale)
    }
}

Then in your MKMapViewDelegate, keep a reference to your tileRenderer and implement the rendererForOverlay :

var tileRenderer: MKTileOverlayRenderer?

public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    if let polyline = overlay as? MKPolyline {
        let lineRenderer = NXPolylineRenderer(overlay: overlay)
        lineRenderer.tileRenderer = tileRenderer
        // Configure your polyline overlay renderer here...
        return lineRenderer;
    }

    if let tileOverlay = overlay as? MKTileOverlay {
        if tileRenderer == nil || tileRenderer.overlay != overlay {
            tileRenderer = MKTileOverlayRenderer(overlay: overlay)
        }
        return tileRenderer
    }

    return MKOverlayRenderer(overlay: overlay)
}

All credits for the idea goes to @Jonathan, I'm just posting a swift ready to copy/paste code for newcomers.