简体   繁体   中英

How to animate a fill color change on UIBezierPath?

The shape is filled in correctly to begin with but I can't figure out how to change the fill color or, even better, how to animate a fill color change on a UIBezierPath . Just like changing the background color of a UIView is what I'm looking for.

var fillColor = UIColor()

func changeBackgroundColor() {

    let animcolor = CABasicAnimation(keyPath: "fillColor")
    animcolor.fromValue = UIColor.greenColor()
    animcolor.toValue = UIColor.orangeColor()
    animcolor.duration = 1.0;
    animcolor.repeatCount = 0;
    animcolor.autoreverses = true
    shapeLayer.addAnimation(animcolor, forKey: "fillColor")

}


var fillColor = UIColor()
let clipPath = UIBezierPath()
let shapeLayer = CAShapeLayer()

override func drawRect(rect: CGRect) {

        clipPath.moveToPoint(CGPointMake(self.bounds.minX + 7.65, self.bounds.minY - 0.25))
        clipPath.addCurveToPoint(CGPointMake(self.bounds.minX + 7.65, self.bounds.minY + 36.1), controlPoint1: CGPointMake(self.bounds.minX - 2.38, self.bounds.minY + 9.79), controlPoint2: CGPointMake(self.bounds.minX - 2.38, self.bounds.minY + 26.06))
        clipPath.addCurveToPoint(CGPointMake(self.bounds.minX + 43.99, self.bounds.minY + 36.1), controlPoint1: CGPointMake(self.bounds.minX + 17.69, self.bounds.minY + 46.13), controlPoint2: CGPointMake(self.bounds.minX + 33.96, self.bounds.minY + 46.13))
        clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 43.99, self.bounds.minY + 36.1))
        clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 44.01, self.bounds.minY + 0.19))
        clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 7.58, self.bounds.minY + 0.19))
        clipPath.usesEvenOddFillRule = true
        fillColor = userColor
    }

clipPath.addClip()
fillColor.setFill()
clipPath.fill()
shapeLayer.path = clipPath.CGPath
self.layer.mask = shapeLayer

Just like changing the background color of a UIView is what I'm looking for.

But why can't you do exactly that? You're already masking the view to your path – so animating your background color would achieve the exact effect you want. Just get rid of the Core Graphics path filling – and also remove the drawRect override, as you can't use drawRect with an animation of the background .

If you really need to use drawRect and animate the color, you could consider splitting up your UIView into two UIViews - one to draw the background layer (which you could animate), and one to draw the custom drawing. Alternatively, you could setup a CADisplayLink to re-draw the view every frame with an intermediate background color – but that's not amazing in terms of performance.

As I say below, you can't animate the fill color of a UIBezierPath directly. If you only want to change (instead of animate) the fill color with your existing code, then you'd just want to call setNeedsDisplay with a different userColor .

If you're asking this because you're using two different paths for the masking and filling, see my below (overcomplicated in hindsight) answer.


The problem is the fill color of a UIBezierPath isn't animatable. But the fill color on a CAShapeLayer is .

Therefore you want to use another CAShapeLayer for the filling instead. I suggest you create a fillLayer and a maskLayer property in order to make their functions clear. Now because you're using a 100% layered approach, you can move your code out of the drawRect , as you're no longer doing any Core Graphics drawing.

Also, it's worth noting that you need to use a CGColor for the animation to and from values.

Something like this should do the trick:

var fillColor = UIColor.greenColor()
let maskLayer = CAShapeLayer()
let fillLayer = CAShapeLayer()

func changeBackgroundColor() {

    let animcolor = CABasicAnimation(keyPath: "fillColor")
    animcolor.fromValue = UIColor.greenColor().CGColor
    animcolor.toValue = UIColor.orangeColor().CGColor
    animcolor.duration = 1.0;
    animcolor.repeatCount = 0;
    animcolor.autoreverses = true
    fillLayer.addAnimation(animcolor, forKey: "fillColor")

}

// re-adjust the clipping path when the view bounds changes
override func layoutSubviews() {

    let clipPath = UIBezierPath()
    clipPath.moveToPoint(CGPointMake(self.bounds.minX + 7.65, self.bounds.minY - 0.25))
    clipPath.addCurveToPoint(CGPointMake(self.bounds.minX + 7.65, self.bounds.minY + 36.1), controlPoint1: CGPointMake(self.bounds.minX - 2.38, self.bounds.minY + 9.79), controlPoint2: CGPointMake(self.bounds.minX - 2.38, self.bounds.minY + 26.06))
    clipPath.addCurveToPoint(CGPointMake(self.bounds.minX + 43.99, self.bounds.minY + 36.1), controlPoint1: CGPointMake(self.bounds.minX + 17.69, self.bounds.minY + 46.13), controlPoint2: CGPointMake(self.bounds.minX + 33.96, self.bounds.minY + 46.13))
    clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 43.99, self.bounds.minY + 36.1))
    clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 44.01, self.bounds.minY + 0.19))
    clipPath.addLineToPoint(CGPointMake(self.bounds.minX + 7.58, self.bounds.minY + 0.19))

    // update layer paths
    fillLayer.path = clipPath.CGPath
    maskLayer.path = clipPath.CGPath

}

override init(frame: CGRect) {
    super.init(frame: frame)

    // configure filling
    fillLayer.fillColor = fillColor.CGColor
    fillLayer.fillRule = kCAFillRuleEvenOdd

    // add fill path to superlayer
    layer.addSublayer(fillLayer)

    // configure masking layer
    maskLayer.fillRule = kCAFillRuleEvenOdd
    layer.mask = maskLayer
}

Swift 4 version, you can check code in playground

 var fillColor = UIColor.green let maskLayer = CAShapeLayer() let fillLayer = CAShapeLayer() func changeBackgroundColor() { let animcolor = CABasicAnimation(keyPath: "fillColor") animcolor.fromValue = UIColor.green.cgColor animcolor.toValue = UIColor.orange.cgColor animcolor.duration = 1.0; animcolor.repeatCount = 0; animcolor.autoreverses = true fillLayer.add(animcolor, forKey: "fillColor") } // re-adjust the clipping path when the view bounds changes let view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300)) view.backgroundColor = UIColor.white func createPaths() { let clipPath = UIBezierPath() clipPath.move(to: CGPoint(x: view.bounds.minX + 7.65, y: view.bounds.minY - 0.25)) clipPath.addCurve(to: CGPoint(x: view.bounds.minX + 7.65, y: view.bounds.minY + 36.1), controlPoint1: CGPoint(x: view.bounds.minX - 2.38, y: view.bounds.minY + 9.79), controlPoint2: CGPoint(x: view.bounds.minX - 2.38, y: view.bounds.minY + 26.06)) clipPath.addCurve(to: CGPoint(x: view.bounds.minX + 43.99, y: view.bounds.minY + 36.1), controlPoint1: CGPoint(x: view.bounds.minX + 17.69, y: view.bounds.minY + 46.13), controlPoint2: CGPoint(x: view.bounds.minX + 33.96, y: view.bounds.minY + 46.13)) clipPath.addLine(to: CGPoint(x: view.bounds.minX + 43.99, y: view.bounds.minY + 36.1)) clipPath.addLine(to: CGPoint(x: view.bounds.minX + 44.01, y: view.bounds.minY + 0.19)) clipPath.addLine(to: CGPoint(x: view.bounds.minX + 7.58, y: view.bounds.minY + 0.19)) // update layer paths fillLayer.path = clipPath.cgPath maskLayer.path = clipPath.cgPath fillLayer.fillColor = fillColor.cgColor fillLayer.fillRule = .evenOdd view.layer.addSublayer(fillLayer) maskLayer.fillRule = .evenOdd view.layer.mask = maskLayer } createPaths() 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM