5

I'm looking for a way to draw a line with a linear gradient along it's path using CGContextRef. I've tried several approaches but most are focussed on filling an area (rect) with an gradient which is not what I need.

I use the following code to draw a solid line using a CGPathRef. What I actually need is a smooth linear gradient starting with color1 at the the first point of the line and ending with color2 at the last point of the line.

Does anyone know how to achieve this?

- (CGMutablePathRef)generatedPath
{
    CGMutablePathRef path = CGPathCreateMutable();

    CGPathMoveToPoint(path, nil, 20, 10);
    CGPathAddLineToPoint(path, nil, 100, 100);
    CGPathAddLineToPoint(path, nil, 140, 120);
    CGPathAddLineToPoint(path, nil, 160, 100);
    CGPathAddLineToPoint(path, nil, 210, 70);
    CGPathAddLineToPoint(path, nil, 250, 10);

    return path;
}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGMutablePathRef path = [self generatedPath];
    CGContextAddPath(ctx, path);

    CGColorRef color = [UIColor redColor].CGColor;

    CGContextSetLineWidth(ctx, 8.0);

    CGContextSetStrokeColorWithColor(ctx, color);
    CGContextStrokePath(ctx);
}

=========UPDATE================================================

I actually found this question which is also VERY similar to what I need: Gradient Polyline with MapKit ios

I used the following code to achieve a gradient following the path of the line. As a remark don't mind the inefficient way of obtaining and passing around the path points. I'll optimize that later:

- (NSArray*)generatePathPoints {
    NSMutableArray *points = [[NSMutableArray alloc] init];

    [points addObject:[NSValue valueWithCGPoint:CGPointMake(20, 50)]];
    [points addObject:[NSValue valueWithCGPoint:CGPointMake(100, 100)]];
    [points addObject:[NSValue valueWithCGPoint:CGPointMake(140, 120)]];
    [points addObject:[NSValue valueWithCGPoint:CGPointMake(160, 100)]];
    [points addObject:[NSValue valueWithCGPoint:CGPointMake(210, 70)]];
    [points addObject:[NSValue valueWithCGPoint:CGPointMake(250, 10)]];

    return points;
}

- (CGMutablePathRef)pathFromPoints: (NSArray*)points;
{
    CGMutablePathRef path = CGPathCreateMutable();
    for (int i = 0; i < points.count; i++) {
        NSValue *pointValue = points[i];
        CGPoint point = [pointValue CGPointValue];
        if(i == 0){
            CGPathMoveToPoint(path, nil, point.x, point.y);
        }
        else{
            CGPathAddLineToPoint(path, nil, point.x, point.y);
        }
    }

    return path;
}

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();

    NSArray *points = [self generatePathPoints];

    CGFloat widthOfLine = 8.0;
    CGLineCap lineCap = kCGLineCapRound;
    CGLineJoin lineJoin = kCGLineJoinRound;
    int miterLimit = 10;

    UIColor *ccolor, *pcolor;
    for (int i=0;i< points.count;i++){
        CGMutablePathRef path = CGPathCreateMutable();
        CGPoint point = ((NSValue*)points[i]).CGPointValue;
        ccolor = [self colorForPointAtIndex:i points:points];
        if (i==0){
            CGPathMoveToPoint(path, nil, point.x, point.y);
        } else {
            CGPoint prevPoint = ((NSValue*)points[i-1]).CGPointValue;
            CGPathMoveToPoint(path, nil, prevPoint.x, prevPoint.y);
            CGPathAddLineToPoint(path, nil, point.x, point.y);
            CGFloat pc_r,pc_g,pc_b,pc_a,
            cc_r,cc_g,cc_b,cc_a;
            [pcolor getRed:&pc_r green:&pc_g blue:&pc_b alpha:&pc_a];
            [ccolor getRed:&cc_r green:&cc_g blue:&cc_b alpha:&cc_a];
            CGFloat gradientColors[8] = {pc_r,pc_g,pc_b,pc_a,
                cc_r,cc_g,cc_b,cc_a};

            CGFloat gradientLocation[2] = {0,1};
            CGContextSaveGState(context);
            CGFloat lineWidth = CGContextConvertSizeToUserSpace(context, (CGSize){widthOfLine,widthOfLine}).width;
            CGPathRef pathToFill = CGPathCreateCopyByStrokingPath(path, NULL, lineWidth, lineCap, lineJoin, miterLimit);
            CGContextAddPath(context, pathToFill);
            CGContextClip(context);//<--clip your context after you SAVE it, important!
            CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
            CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradientColors, gradientLocation, 2);
            CGColorSpaceRelease(colorSpace);
            CGPoint gradientStart = prevPoint;
            CGPoint gradientEnd = point;
            CGContextDrawLinearGradient(context, gradient, gradientStart, gradientEnd, kCGGradientDrawsAfterEndLocation | kCGGradientDrawsBeforeStartLocation);
            CGGradientRelease(gradient);
            CGContextRestoreGState(context);//<--Don't forget to restore your context.
        }
        pcolor = [UIColor colorWithCGColor:ccolor.CGColor];
    }
}

-(UIColor*) colorForPointAtIndex: (NSUInteger)index points:(NSArray*)points {
    return [UIColor colorWithRed:1.0 green:0 blue:0 alpha:(1.0 / points.count) * index];
}

This produces ALMOST what I need. There is only tiny glitch though: https://i.stack.imgur.com/F9Efx.jpg

As you can see the line endings are overlapping which would normally not be an issue. But because the lines are transparent I get this unwanted side-effect.

Does any one know how to go from here?

Community
  • 1
  • 1
Mattijs
  • 51
  • 1
  • 3
  • I found this answer is VERY close to what I am looking for: http://stackoverflow.com/questions/20632365/draw-gradient-along-a-curved-uibezierpath But this answer draws a gradient in a certain direction where the gradient is clipped using a path. While this is very close to what I need, the difference is that in my case the gradient really needs to follow the path. So let's say I'd have a line of 10px width in a shape of 3/4 a circle, the gradient needs to start at the beginning of the line and at the end of the line. – Mattijs Oct 15 '14 at 07:22

0 Answers0