Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apply gradient color to arc created with UIBezierPath

I want to create a progress bar that has the form of an arc. The color of the progress bar has to change according to the values.

I created an arc using UIBezierPath bezierPathWithArcCenter. I used the following code:

- (void)viewDidLoad
{
    [super viewDidLoad];

    int radius = 100;

    CAShapeLayer *arc = [CAShapeLayer layer];

    arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:60.0 endAngle:0.0 clockwise:YES].CGPath;

    arc.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
                                  CGRectGetMidY(self.view.frame)-radius);

    arc.fillColor = [UIColor clearColor].CGColor;
    arc.strokeColor = [UIColor purpleColor].CGColor;
    arc.lineWidth = 15;

    [self.view.layer addSublayer:arc];

    CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    drawAnimation.duration            = 5.0; // "animate over 10 seconds or so.."
    drawAnimation.repeatCount         = 1.0;  // Animate only once..
    drawAnimation.removedOnCompletion = NO;   // Remain stroked after the animation..
    drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
    drawAnimation.toValue   = [NSNumber numberWithFloat:10.0f];
    drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    [arc addAnimation:drawAnimation forKey:@"drawCircleAnimation"];

}

The result looks like this:

enter image description here

My question is: How to apply a gradient to the color if i.e. the value is <= 50%? I also created an UIButton that generates random CGFloat numbers in order to hook it up with the progress bar. Does anyone have an idea how to tackle this?

The gradient would look something like this:

enter image description here

Any help would be appreciated!

Thank you very much.

Granit

like image 951
Granit Avatar asked Dec 17 '13 09:12

Granit


3 Answers

You can use a CAGradientLayer to get the gradient effect, and use the CAShapeLayer as a mask.

e.g.:

- (void)viewDidLoad
{
    [super viewDidLoad];

    int radius = 100;

    CAShapeLayer *arc = [CAShapeLayer layer];    
    arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:60.0 endAngle:0.0 clockwise:YES].CGPath;

    arc.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
                               CGRectGetMidY(self.view.frame)-radius);

    arc.fillColor = [UIColor clearColor].CGColor;
    arc.strokeColor = [UIColor purpleColor].CGColor;
    arc.lineWidth = 15; 
    CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    drawAnimation.duration            = 5.0; // "animate over 10 seconds or so.."
    drawAnimation.repeatCount         = 1.0;  // Animate only once..
    drawAnimation.removedOnCompletion = NO;   // Remain stroked after the animation..
    drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
    drawAnimation.toValue   = [NSNumber numberWithFloat:10.0f];
    drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    [arc addAnimation:drawAnimation forKey:@"drawCircleAnimation"];

    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = self.view.frame;
    gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor blueColor].CGColor ];
    gradientLayer.startPoint = CGPointMake(0,0.5);
    gradientLayer.endPoint = CGPointMake(1,0.5);

    [self.view.layer addSublayer:gradientLayer];
     //Using arc as a mask instead of adding it as a sublayer.
     //[self.view.layer addSublayer:arc]; 
     gradientLayer.mask = arc;


}
like image 174
djshiow Avatar answered Nov 08 '22 06:11

djshiow


enter image description here

To draw a gradient along a stroke, see this implementation: https://stackoverflow.com/a/43668420/917802

EDIT: In short, create a custom UIView class, add a radial gradient to it by iterating between two colours at increasing angles, e.g. colour1 = 1 degree, colour2 = 2 degrees etc, all the way up to 360. Then apply a donut mask to that. As you change the strokeEnd value of the masking CAShapeLayer, you also rotate the underlying radial gradient. Because they move together it looks like the stroke itself has a gradient.

like image 24
Johnny Rockex Avatar answered Nov 08 '22 07:11

Johnny Rockex


The Answers given so far are great, but they are a bit complicated, I think the following logic should be much simpler:

  1. Draw your curve/path of your shape layer .
  2. Close the path on itself, i.e draw the same curve/path but in reverse.
  3. Create a CAShapeLayer and set its path to the path in (2).
  4. Give the shape layer in (3) an arbitrary stroke colour.
  5. Create a CAGradientLayer.
  6. Set the gradient layer mask to the shape layer in (4).
  7. Set the colours of the gradient layer to your desired array.
  8. Add the gradient layer to your original shapeLayer in (1).
func draweCurve(fromPoint: CGPoint, toPoint: CGPoint, x: CGFloat) {
    // ------- 1 --------
    let curveLayer = CAShapeLayer()
    curveLayer.contentsScale = UIScreen.main.scale
    curveLayer.frame = CGRect(origin: .zero, size: CGSize(width: 100, height: 100))
    curveLayer.fillColor = UIColor.red.cgColor
    curveLayer.strokeColor = UIColor.blue.cgColor

    let path = UIBezierPath()
    path.move(to: fromPoint)
    let controlPoint1 = CGPoint(x: fromPoint.x + 40 * 0.45, y: fromPoint.y)
    let controlPoint2 = CGPoint(x: toPoint.x - 40 * 0.45, y: toPoint.y)
    path.addCurve(to: toPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
    curveLayer.path = path.cgPath

    // ------- 2 --------
    // close the path on its self
    path.addCurve(to: fromPoint, controlPoint1: controlPoint2, controlPoint2: controlPoint1)
    addGradientLayer(to: curveLayer, path: path)
}

private func addGradientLayer(to layer: CALayer, path: UIBezierPath) {
    // ------- 3 --------
    let gradientMask = CAShapeLayer()
    gradientMask.contentsScale = UIScreen.main.scale
    // ------- 4 --------
    gradientMask.strokeColor = UIColor.white.cgColor
    gradientMask.path = path.cgPath

    // ------- 5 --------
    let gradientLayer = CAGradientLayer()
    // ------- 6 --------
    gradientLayer.mask = gradientMask
    gradientLayer.frame = layer.frame
    gradientLayer.contentsScale = UIScreen.main.scale
    // ------- 7 --------
    gradientLayer.colors = [UIColor.gray.cgColor, UIColor.green.cgColor]
    // ------- 8 --------
    layer.addSublayer(gradientLayer)
}
like image 3
m.eldehairy Avatar answered Nov 08 '22 05:11

m.eldehairy