Jinghan Wang
Jinghan Wang

Reputation: 1137

MKTileOverlay: How to reload a specific tile or region

I'm working on a map-based app. I used a MKTileOverlay to show some dynamic contents. Occasionally certain tiles need reloading when new information arrives.

I've tried to call overlayRenderer.setNeedsDisplayInMapRect(mapRect) in main thread but apparently it doesn't trigger a map redraw in my case. So far the only approach that works is overlayRenderer.reloadData(). However, this will cause the entire contents (instead of only specific region) to be reloaded and lead to flickering in the view, so this is not a option for me.

Can anyone give any advice on this? Thanks & Cheers.

Upvotes: 2

Views: 1128

Answers (1)

Jinghan Wang
Jinghan Wang

Reputation: 1137

Finally I figured it out. In fact, the overlay's not updating is not due to setNeedsDisplayInMapRect. After several checks I found that setNeedsDisplayInMapRect is actually causing a run of drawMapRect:zoomScale:inContext:, however, somehow a default redraw produces the same tile image. I guess this may due to some internal cache of MKTileOverlayRender.

The solution for me is to subclass the MKTileOverlayRender, and in the new class:

override func canDrawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale) -> Bool {

    let tilePath = self.tilePathForMapRect(mapRect, andZoomScale: zoomScale)
    let tilePathString = stringForTilePath(tilePath)
    if let _ = self.cache.objectForKey(tilePathString) {
        return true
    } else {
        let tileOverlay = self.overlay as! MKTileOverlay
        tileOverlay.loadTileAtPath(tilePath, result: {
            data, error in
            if error == nil && data != nil {
                if let image = UIImage(data: data!) {
                    self.cache.setObject(image, forKey: tilePathString)
                    self.setNeedsDisplayInMapRect(mapRect, zoomScale: zoomScale)
                }
            }
        })

        return false
    }
}

override func drawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale, inContext context: CGContext) {
    let tilePath = self.tilePathForMapRect(mapRect, andZoomScale: zoomScale)
    let tilePathString = stringForTilePath(tilePath)

    if let image = self.cache.objectForKey(tilePathString) as? UIImage {
        CGContextDrawImage(context, self.rectForMapRect(mapRect), image.CGImage)
    } else {
        super.setNeedsDisplayInMapRect(mapRect, zoomScale: zoomScale)
    }

    self.cache.removeObjectForKey(tilePathString)
}

Upvotes: 2

Related Questions