Applying a Gradient to CAShapeLayer

前端 未结 3 1288

Does anyone have any experience in applying a Gradient to a CAShapeLayer? CAShapeLayer is a fantastic layer class, but it appears to only support solid fill coloring, wherea

相关标签:
3条回答
  • 2020-12-04 18:37

    Many thanks to @Palimondo for a great answer!

    In case someone is looking for Swift 4 + filling animation code of this solution:

        let myView = UIView(frame: .init(x: 0, y: 0, width: 200, height: 150))
        view.addSubview(myView)
        myView.center = view.center
    
        // Start and finish point
        let startPoint = CGPoint(x: myView.bounds.minX, y: myView.bounds.midY)
        let finishPoint = CGPoint(x: myView.bounds.maxX, y: myView.bounds.midY)
    
        // Path
        let path = UIBezierPath()
        path.move(to: startPoint)
        path.addLine(to: finishPoint)
    
        // Gradient Mask
        let gradientMask = CAShapeLayer()
        let lineHeight = myView.frame.height
        gradientMask.fillColor = UIColor.clear.cgColor
        gradientMask.strokeColor = UIColor.black.cgColor
        gradientMask.lineWidth = lineHeight
        gradientMask.frame = myView.bounds
        gradientMask.path = path.cgPath
    
        // Gradient Layer
        let gradientLayer = CAGradientLayer()
        gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
        gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
    
        // make sure to use .cgColor
        gradientLayer.colors = [UIColor.red.cgColor, UIColor.green.cgColor]
        gradientLayer.frame = myView.bounds
        gradientLayer.mask = gradientMask
    
        myView.layer.addSublayer(gradientLayer)
    
        // Corner radius
        myView.layer.cornerRadius = 10
        myView.clipsToBounds = true
    

    Extra. In case you also need a "filling animation", add this lines:

        // Filling animation
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.fromValue = 0
        animation.duration = 10
        gradientMask.add(animation, forKey: "LineAnimation")
    
    0 讨论(0)
  • 2020-12-04 18:37

    This is a great solution, but you might encounter unexpected problems if you're creating a category on CAShapeLayer where you don't immediately have the view.

    See Setting correct frame of a newly created CAShapeLayer

    Bottom line, get the bounds of the path then set the gradient mask's frame using the path bounds and translate as necessary. Good thing here is that by using the path's bounds rather than any other frame, the gradient will only fit within the path bounds (assuming that's what you want).

    // Category on CAShapeLayer
    
    CGRect pathBounds = CGPathGetBoundingBox(self.path);
    
    CAShapeLayer *gradientMask = [CAShapeLayer layer];
    gradientMask.fillColor = [[UIColor blackColor] CGColor];
    gradientMask.frame = CGRectMake(0, 0, pathBounds.size.width, pathBounds.size.height);
    gradientMask.path = self.path;
    
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.startPoint = CGPointMake(0.5,1.0);
    gradientLayer.endPoint = CGPointMake(0.5,0.0);
    gradientLayer.frame = CGRectMake(0, 0, pathBounds.size.width, pathBounds.size.height);
    
    NSMutableArray *colors = [NSMutableArray array];
    for (int i = 0; i < 10; i++) {
        [colors addObject:(id)[[UIColor colorWithHue:(0.1 * i) saturation:1 brightness:.8 alpha:1] CGColor]];
    }
    gradientLayer.colors = colors;
    
    [gradientLayer setMask:gradientMask];
    [self addSublayer:gradientLayer];
    
    0 讨论(0)
  • 2020-12-04 18:53

    You could use the path of your shape to create a masking layer and apply that on the gradient layer, like this:

    UIView *v = [[UIView alloc] initWithFrame:self.window.frame];
    
    CAShapeLayer *gradientMask = [CAShapeLayer layer];   
    gradientMask.fillColor = [[UIColor clearColor] CGColor];
    gradientMask.strokeColor = [[UIColor blackColor] CGColor];
    gradientMask.lineWidth = 4;
    gradientMask.frame = CGRectMake(0, 0, v.bounds.size.width, v.bounds.size.height);
    
    CGMutablePathRef t = CGPathCreateMutable();    
    CGPathMoveToPoint(t, NULL, 0, 0);
    CGPathAddLineToPoint(t, NULL, v.bounds.size.width, v.bounds.size.height);
    
    gradientMask.path = t;
    
    
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.startPoint = CGPointMake(0.5,1.0);
    gradientLayer.endPoint = CGPointMake(0.5,0.0);
    gradientLayer.frame = CGRectMake(0, 0, v.bounds.size.width, v.bounds.size.height);
    NSMutableArray *colors = [NSMutableArray array];
    for (int i = 0; i < 10; i++) {
        [colors addObject:(id)[[UIColor colorWithHue:(0.1 * i) saturation:1 brightness:.8 alpha:1] CGColor]];
    }
    gradientLayer.colors = colors;
    
    [gradientLayer setMask:gradientMask];
    [v.layer addSublayer:gradientLayer];
    

    If you want to also use the shadows, you would have to place a "duplicate" of the shape layer under the gradient layer, recycling the same path reference.

    0 讨论(0)
提交回复
热议问题