Draw gradient along a curved UIBezierPath

In an app, I draw a curved UIBezierPath an an MKOverlayPathView class to show flight routes. This is the code I am using:

- (UIBezierPath *)pathForOverlayForMapRect:(MKMapRect)mapRect {

    ... bla bla bla ...

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:s];
    [path addQuadCurveToPoint:e controlPoint:cp1];
    [path addLineToPoint:e2];
    [path addQuadCurveToPoint:s2 controlPoint:cp2];
    [path closePath];

    return path;   
  }
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context{

    self.mapRect = mapRect;

    CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0);
    CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0);
    CGContextSetLineWidth(context, mapRect.size.height/700);
    CGContextSetLineJoin(context, kCGLineJoinRound);
    CGContextSetLineCap(context, kCGLineCapRound);

    CGContextAddPath(context, [self pathForOverlayForMapRect:mapRect].CGPath);

    [self updateTouchablePathForMapRect:mapRect];

    CGContextDrawPath(context, kCGPathFillStroke);

}

This is working just fine but I would like to draw a gradient along that path instead of just a fill color. And this is where it is starting to get very tricky.

I have experimented with CGContextDrawLinearGradient() but it hasn't got me anywhere useful yet.

Answers


The trick is to use the stroke path of the line (CGContextReplacePathWithStrokedPath) and clip it (CGContextClip) to restrict the gradient to the path:

// Create a gradient from white to red
CGFloat colors [] = {
    1.0, 1.0, 1.0, 1.0,
    1.0, 0.0, 0.0, 1.0
};

CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;

CGContextSetLineWidth(context, mapRect.size.height/700);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);

CGContextAddPath(context, [self pathForOverlayForMapRect:mapRect].CGPath);
CGContextReplacePathWithStrokedPath(context);
CGContextClip(context);

[self updateTouchablePathForMapRect:mapRect];

// Define the start and end points for the gradient
// This determines the direction in which the gradient is drawn
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));

CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient), gradient = NULL;

In Swift 3 this can be achieved using the code below. This example is for a straight line but the same principles should apply.

    let startPoint = CGPoint(x:100, y:100)
    let endPoint = CGPoint(x: 300, y:400)

    let context = UIGraphicsGetCurrentContext()!
    context.setStrokeColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0);
    // create a line
    context.move(to: startPoint)
    context.addLine(to: endPoint)
    context.setLineWidth(4)
    // use the line created above as a clipping mask
    context.replacePathWithStrokedPath()
    context.clip()

    // create a gradient
    let locations: [CGFloat] = [ 0.0, 0.5 ]

    let colors = [UIColor.green.cgColor,
                  UIColor.white.cgColor]

    let colorspace = CGColorSpaceCreateDeviceRGB()

    let gradient = CGGradient(colorsSpace: colorspace,
                              colors: colors as CFArray, locations: locations)

    let gradientStartPoint = CGPoint(x: rect.midX, y: rect.minY)
    let gradientEndPoint = CGPoint(x: rect.midX, y: rect.maxY)

    context.drawLinearGradient(gradient!,
                                start: gradientStartPoint, end: gradientEndPoint,
                                options: .drawsBeforeStartLocation)
    UIGraphicsEndImageContext()

Need Your Help

Make bootstrap popover work with custom html template

jquery html css twitter-bootstrap-3

I am using an input group textbox and I need the Bootstrap 3 popover to work and the popover template should be defined &n designed by me.

How do you color a rectangle in C# that has been declared in XAML in WPF?

c# wpf xaml visual-studio-2010

How do you color a rectangle in C# that has been declared in XAML in WPF?