Search code examples
iosobjective-cgradientuibezierpath

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.


Solution

  • 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;