3

I have built a custom MKOverlayRenderer in order to build polygons, apply a blend mode, then add them to the map view. In my drawMapRect function, I use an array of CGPoints to build the polygon, and create a path.

However, during runtime nothing shows on my map view. My best guess would be the order of which I create the polygon in my drawMapRect function. Any help or guidance would be greatly appreciated, thanks!

override func drawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale, inContext context: CGContext) {

    super.drawMapRect(mapRect, zoomScale: zoomScale, inContext: context)
    CGContextSaveGState(context)
    CGContextSetBlendMode(context, CGBlendMode.Exclusion)
    CGContextSetFillColorWithColor(context, self.fillColor)
    CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor)
    CGContextSetLineWidth(context, 1.0)

    let point = MKMapPointForCoordinate(self.polygon.points[0])
    CGContextMoveToPoint(context, CGFloat(point.x), CGFloat(point.y))
    for i in 1..<self.polygon.points.count {
        let point = polygon.points[i]
        let p = MKMapPointForCoordinate(point)
        CGContextAddLineToPoint(context, CGFloat(p.x), CGFloat(p.y))
    }

    CGContextClosePath(context)
    CGContextDrawPath(context, CGPathDrawingMode.FillStroke)
    CGContextRestoreGState(context)
}

Here is where my custom overlay renderer is initialized:

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
    if (overlay is MKPolygon) {
        let polygonRenderer = MyCustomMapRenderer(overlay: overlay, fillColor: self.polyFactory!.getPolygonColor().CGColor, polygon: self.currentPolygon!)
        return polygonRenderer
    }
    return MKOverlayRenderer()
}
Jamesar
  • 47
  • 3
  • 8
  • 1
    Have you confirmed that the annotation has been added correctly and that it's a `MKPolygon`? E.g. add breakpoint to `rendererForOverlay` and make sure you're hitting that piece of code. If so, add breakpoint to `drawMapRect` and make sure that's getting called. Sometimes it's something as simple as neglecting to set the delegate of the map view. Let's confirm that's OK before worrying about this code... – Rob May 27 '16 at 01:02
  • @Rob Yep, they are both definitely getting called. It will hit the `rendererForOverlay's` return before proceeding into `drawMapRect` – Jamesar May 27 '16 at 01:09

3 Answers3

2

A few observations:

  • You are taking points, which is already an array of MKMapPoint (in MKPolygonRenderer, at least) and calling MKMapPointForCoordinate. If you want to use MKMapPointForCoordinate, you'd pass it coordinates, not points. Maybe you've defined your own properties, but I might change the name of the property to avoid confusion.

  • You need to convert map points to screen points with pointForMapPoint.

  • Also, you are calling CGContextSetFillColorWithColor, but passing it fillColor. But assuming your class is a subclass of MKPolygonRenderer, the fillColor is a UIColor, not a CGColor.

  • You seem to be accessing some property, points, but the MKPolygonRenderer doesn't have points property, but rather points() method.

With all of that said, I'm unclear how your code even compiled. I suspect you didn't subclass from MKPolygonRenderer, but rather subclassed MKOverlayRenderer and then implemented a bunch of properties yourself? If you subclass MKPolygonRender, you get all the polygon behavior for free and only have to implement drawMapRect:

class MyCustomMapRenderer: MKPolygonRenderer {

    override func drawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale, inContext context: CGContext) {
        //super.drawMapRect(mapRect, zoomScale: zoomScale, inContext: context)

        CGContextSaveGState(context)
        CGContextSetBlendMode(context, CGBlendMode.Exclusion)
        CGContextSetFillColorWithColor(context, fillColor!.CGColor)
        CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor)
        CGContextSetLineWidth(context, 1.0)

        if polygon.pointCount > 1 {
            CGContextBeginPath(context)

            let point = pointForMapPoint(polygon.points()[0])
            CGContextMoveToPoint(context, CGFloat(point.x), CGFloat(point.y))
            for i in 1 ..< polygon.pointCount {
                let point = pointForMapPoint(polygon.points()[i])
                CGContextAddLineToPoint(context, CGFloat(point.x), CGFloat(point.y))
            }

            CGContextClosePath(context)
            CGContextDrawPath(context, CGPathDrawingMode.FillStroke)
        }
        CGContextRestoreGState(context)
    }

}

By the way, we probably could be more sophisticated here (checking to see if the points were visible, figuring out what points to render given the scale ... i.e. if polygon with thousands of points and being scaled into a 100x100 portion of the view, perhaps you don't have to render all of the points, etc.). See WWDC 2010 Customizing Maps with Overlays, which, while dated, is still relevant.

As an aside, your rendererForOverlay is curious, too. You are calling some custom initialization method (which is fine), but you're passing overlay and currentPolygon. But the overlay is the polygon, so I don't know what this currentPolygon is. And rendererForOverlay is stateless, so I'd discourage you from referencing some property, but rather just take the overlay that was passed to the method. That way, you could have multiple polygons and let map view keep track of which is which. So I'd do something like:

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
    if let polygon = overlay as? MKPolygon {
        let polygonRenderer = MyCustomMapRenderer(polygon: polygon)
        polygonRenderer.fillColor = polyFactory!.getPolygonColor()
        return polygonRenderer
    }
    fatalError("Unexpected overlay type")
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 1
    Well, that certainly did the trick. Thanks @Rob! I believe the problem was that I was subclassing MKOverlayRenderer and just making things more difficult. In switching to subclassing MKPolygonRenderer, things work great! Going forward, I realize many of my property and variable names are somewhat contradictory or overly confusing. I'll definitely rename and refactor those now that the process is working properly. Again, thanks for your help! – Jamesar May 27 '16 at 17:36
1

Rob's answer in Swift 4

class MyCustomMapRenderer: MKPolygonRenderer {

    override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {
        //super.drawMapRect(mapRect, zoomScale: zoomScale, inContext: context)

        context.saveGState()
        context.setBlendMode(CGBlendMode.exclusion)
        context.setFillColor(fillColor!.cgColor)
        context.setStrokeColor(UIColor.white.cgColor)
        context.setLineWidth(1.0)

        if polygon.pointCount > 1 {
            context.beginPath()

            let point = self.point(for: polygon.points()[0])
            context.move(to: point)
            for i in 1 ..< polygon.pointCount {
                let point = self.point(for: polygon.points()[i])
                context.addLine(to: point)
            }

            context.closePath()
            context.drawPath(using: CGPathDrawingMode.fillStroke)
        }
        context.restoreGState()
    }

}
0

Rob's answer in Swift 3

class MyRenderer: MKPolylineRenderer {

    override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) {

        context.saveGState()
        context.setBlendMode(CGBlendMode.exclusion)

        let clear = UIColor.clear

        context.setFillColor(clear.cgColor)

        let green = UIColor.green

        context.setStrokeColor(green.cgColor)

        context.setLineWidth(10.0)

        if self.polyline.pointCount > 1 {

            context.beginPath()

            let point_ = self.point(for: self.polyline.points()[0])

            context.move(to: CGPoint(x: point_.x, y: point_.y))

            for element in 1 ..< self.polyline.pointCount {

                let point_ = self.point(for: polyline.points()[element])

                context.addLine(to: CGPoint(x: point_.x, y: point_.y))

            }

            context.closePath()

            context.drawPath(using: .fillStroke)

        }

        context.restoreGState()

    }


 }
David Seek
  • 16,783
  • 19
  • 105
  • 136