11

I found out how to draw a cricle around map annotation. I do it like this:

     MKCircle *circle = [MKCircle circleWithCenterCoordinate:theCoordinate radius:15000];
     [myMap addOverlay:circle];

 -(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
 {
    MKCircleView *circleView = [[MKCircleView alloc] initWithOverlay:overlay];
    circleView.fillColor =[UIColor redColor];

   return circleView;
}

It works ok but i would like to draw a circle whose fill color is not solid like this:

enter image description here

DixieFlatline
  • 7,895
  • 24
  • 95
  • 147

6 Answers6

10

Answer for iOS 7 using MKCircleRenderer...

You should subclass MKCircleRenderer, and override the fillPath:inContext method, similarly to the accepted answer to this question. e.g.

@implementation MKGradientCircleRenderer

- (void)fillPath:(CGPathRef)path inContext:(CGContextRef)context
{

    CGRect rect = CGPathGetBoundingBox(path);

    CGContextAddPath(context, path);
    CGContextClip(context);

    CGFloat gradientLocations[2] = {0.6f, 1.0f};
    // Start color white with 0.25 alpha,
    // End color green with 0.25 alpha
    CGFloat gradientColors[8] = {1.0f, 1.0f, 1.0f, 0.25f, 0.0f, 1.0f, 0.0f, 0.25f};
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradientColors, gradientLocations, 2);
    CGColorSpaceRelease(colorSpace);

    CGPoint gradientCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
    CGFloat gradientRadius = MIN(rect.size.width, rect.size.height) / 2;

    CGContextDrawRadialGradient(context, gradient, gradientCenter, 0, gradientCenter, gradientRadius, kCGGradientDrawsAfterEndLocation);

    CGGradientRelease(gradient);


}

And then in your MKMapView delegate, implement the following method...

-(MKOverlayRenderer *)mapView:(MKMapView*)mapView rendererForOverlay:(id<MKOverlay>)overlay {

    MKCircle * circle = (MKCircle *)overlay;

    MKGradientCircleRenderer * renderer = [[MKGradientCircleRenderer alloc] initWithCircle:circle];

    return renderer;

}

This will allow you to achieve the same effect, but using the new methods available in iOS 7.

Sammio2
  • 7,422
  • 7
  • 34
  • 49
7

To draw a circle with a gradient, you have to provide an own annotation view class, as none of the existing ones support that. What you can do is you can override the method - (void)fillPath:(CGPathRef)path inContext:(CGContextRef)context in a subclass of MKCircleView. Here is some code (non-optimized, with hardcoded fill parameters) to get you started:

@interface TWOGradientCircleView : MKCircleView
@end

@implementation TWOGradientCircleView

- (void)fillPath:(CGPathRef)path inContext:(CGContextRef)context
{
    CGRect rect = CGPathGetBoundingBox(path);

    CGContextAddPath(context, path);
    CGContextClip(context);

    CGFloat gradientLocations[2] = {0.6f, 1.0f};
    // Start color white with 0.25 alpha,
    // End color green with 0.25 alpha
    CGFloat gradientColors[8] = {1.0f, 1.0f, 1.0f, 0.25f, 0.0f, 1.0f, 0.0f, 0.25f};
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradientColors, gradientLocations, 2);
    CGColorSpaceRelease(colorSpace);

    CGPoint gradientCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
    CGFloat gradientRadius = MIN(rect.size.width, rect.size.height) / 2;

    CGContextDrawRadialGradient(context, gradient, gradientCenter, 0, gradientCenter, gradientRadius, kCGGradientDrawsAfterEndLocation);

    CGGradientRelease(gradient);
}

To use it, just replace MKCircleView with TWOGradientCircleView:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
{
    MKCircleView *circleView = [[TWOGradientCircleView alloc] initWithOverlay:overlay];

    return circleView;
}

If you would like to use an image instead of drawing the gradient, you can replace the gradient drawing above with image drawing. As zooming in would then blur the image, you should either disable zoom, or tile the image as Apple demonstrated in a session at WWDC10 (see here for a repository with their sample code). Setting a UIColor with a pattern image does not work for a radius of 15000 (unless you use a really, really huge image ;)).

Tammo Freese
  • 10,514
  • 2
  • 35
  • 44
  • you need to replace interface TWOGradientCircleView : MKCircleView with interface TWOGradientCircleView : MKCircleRenderer for this to work on iOS7+ (and iOS8+ is explicitly denied to use MKCircleView) – maninvan Feb 23 '16 at 23:44
4

To implement the solution in Swift 2.0 (iOS7+) I used the following solution

import Foundation
import MapKit

class TWOGradientCircleRenderer: MKCircleRenderer {

    override func fillPath(path: CGPath, inContext context: CGContext) {
        let rect:CGRect = CGPathGetBoundingBox(path)

        CGContextAddPath(context, path);
        CGContextClip(context);

        let gradientLocations: [CGFloat]  = [0.6, 1.0];
        let gradientColors: [CGFloat] = [1.0, 1.0, 1.0, 0.25, 0.0, 1.0, 0.0, 0.25];
        let colorSpace = CGColorSpaceCreateDeviceRGB();
        let gradient = CGGradientCreateWithColorComponents(colorSpace, gradientColors, gradientLocations, 2);

        let gradientCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
        let gradientRadius = min(rect.size.width, rect.size.height) / 2;

        CGContextDrawRadialGradient(context, gradient, gradientCenter, 0, gradientCenter, gradientRadius, .DrawsAfterEndLocation);
    }
}

And in your MKMapViewDelegate you will need to add

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
        if overlay is MKCircle {
            let circleRenderer = TWOGradientCircleRenderer(overlay: overlay)
            return circleRenderer
        } else {
            return MKOverlayRenderer()
        }
    }
maninvan
  • 890
  • 9
  • 10
3

Have you tried setting the fill colour to a colour created with [UIColor colorWithPatternImage:]?

Jonathan Grynspan
  • 43,286
  • 8
  • 74
  • 104
  • That would allow to use a pattern, but not the gradient shown in the question. Even if you would store the whole gradient as a bitmap image and convince `UIImage` to make the pattern big enough, zooming in would either lead to a blurry/pixelated overlay, or require a huge overlay image. – Tammo Freese Feb 07 '13 at 11:25
  • He asked about an image, not a gradient. :) – Jonathan Grynspan Feb 07 '13 at 16:04
  • :) In the title, in the text it's about drawing a circle. – Tammo Freese Feb 07 '13 at 17:00
  • It's really up to @DixieFlatline to clarify the question. – Jonathan Grynspan Feb 07 '13 at 19:55
  • That's right. Could you clarify your answer into a working solution? I tried it before writing up my answer, and it hasn't worked for me. – Tammo Freese Feb 07 '13 at 20:22
  • If you have a question, you should ask it rather than piggy-backing on another question in comments. That way, other people can see your question separately and provide their own answers. – Jonathan Grynspan Feb 08 '13 at 00:45
  • Your answer was accepted, but does not provide a working example, and I doubt it works at all for the original question. There is no better point for the clarification than here. – Tammo Freese Feb 08 '13 at 06:08
  • My answer was not accepted; the bounty ran out because @DixieFlatline did not return to the question. If you have your own question, you should ask it. – Jonathan Grynspan Feb 08 '13 at 06:18
  • Then be glad that you got 50 points for an answer that does not work. Let's leave it at that. – Tammo Freese Feb 08 '13 at 06:52
2

This answer is for Swift 3.0 / iOS 10.

class CircleRenderer: MKCircleRenderer {
    override func fillPath(_ path: CGPath, in context: CGContext) {
        let rect: CGRect = path.boundingBox
        context.addPath(path)
        context.clip()
        let gradientLocations: [CGFloat]  = [0.6, 1.0]
        let gradientColors: [CGFloat] = [1.0, 1.0, 1.0, 0.25, 0.0, 1.0, 0.0, 0.25]
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        guard let gradient = CGGradient(colorSpace: colorSpace, colorComponents: gradientColors, locations: gradientLocations, count: 2) else { return }

        let gradientCenter = CGPoint(x: rect.midX, y: rect.midY)
        let gradientRadius = min(rect.size.width, rect.size.height) / 2
        context.drawRadialGradient(gradient, startCenter: gradientCenter, startRadius: 0, endCenter: gradientCenter, endRadius: gradientRadius, options: .drawsAfterEndLocation)
    }
}

And the overlay delegate method:

func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    let circleRenderer = CircleRenderer(overlay: overlay)

    return circleRenderer
}

An example of this at work:

enter image description here

CodeBender
  • 35,668
  • 12
  • 125
  • 132
1

** Only Updating answer:

In IOS 7 fillPath:inContext was deprecated to MKCircleView. Try to use MKCircleRenderer instead.

https://developer.apple.com/library/ios/documentation/MapKit/Reference/MKOverlayPathRenderer_class/Reference/Reference.html#//apple_ref/occ/cl/MKOverlayPathRenderer

seufagner
  • 1,290
  • 2
  • 18
  • 25