Search code examples
iosswiftuiviewuiviewanimationcashapelayer

Why is my CAShapeLayer not animating correctly with the UIView (w/ gif)?


enter image description here

Okay so here's how it goes.

The gray area is a UIView, and masked it with CAShapeLayer. I appended two UIBezierPath and used the Even Odd Rule to remove a portion of that gray area. That is why it shows a line as if it's demonstrating a semi-circle on the left.

The red background is just container view.

Now my problem is that when I animate my UIView, it's as if the mask isn't updating correctly with animating UIView.

Here's the UIView animation part:

func didCompleteItem() {
    self.bar.plainProgressBarLeftAnchor?.constant = self.bar.bounds.width * (self.completedItemCount*self.barMeterMultiplier) / self.bar.bounds.width - self.bar.meterCornerRadius
    UIView.animate(withDuration: 1.0, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations: {
        self.bar.layoutIfNeeded()

    }, completion: nil)
}

Now, here's the layoutSubviews part of it, if you need to see how the CAShapeLayer was created.

override func layoutSubviews() {
    super.layoutSubviews()

    backgroundColor = GradientColor(.leftToRight, frame: self.bounds, colors: GradientFlatRed())
    layer.masksToBounds = true
    layer.cornerRadius = meterCornerRadius

    createMask()

}

var shapeLayer: CAShapeLayer = CAShapeLayer()
func createMask() {
    let bPath = UIBezierPath(roundedRect: plainProgressBar.bounds, byRoundingCorners: [.topRight, .bottomRight], cornerRadii: CGSize(width: 0, height: 0))
    let semiCircle = UIBezierPath(arcCenter: CGPoint(x: bounds.origin.x, y: plainProgressBar.bounds.origin.y+meterCornerRadius), radius: meterCornerRadius, startAngle: 3 * CGFloat.pi / 2, endAngle: CGFloat.pi / 2, clockwise: true)
    shapeLayer.frame = layer.bounds
    bPath.append(semiCircle)
    shapeLayer.path = bPath.cgPath
    shapeLayer.fillRule = kCAFillRuleEvenOdd
    plainProgressBar.layer.mask = shapeLayer
}

Solution

  • Drawing a stretchable circle and changing the width of the image view that portrays the circle would give you the effect you're after, pretty much without effort and with none of the artifacts you're experiencing:

    enter image description here