Search code examples
swiftuiviewanimationuibezierpathcabasicanimationios-animations

Animate view borders with custom UIBezierPath


I'm trying to make an app with chat "bubbles" and I need to animate a transition between each bubble state.

extension UIView {
    func makeRoundedCorners(topLeftRad: Double, topRightRad: Double, bottomRightRad: Double, bottomLeftRad: Double) {
        let path = UIBezierPath(roundedRect: self.bounds, topLeftRadius: CGFloat(topLeftRad), topRightRadius: CGFloat(topRightRad), bottomRightRadius: CGFloat(bottomRightRad), bottomLeftRadius: CGFloat(bottomLeftRad))
        let maskLayer = CAShapeLayer()
        maskLayer.path = path.cgPath

        let animation = CABasicAnimation(keyPath: "path")
        animation.toValue = path.cgPath
        animation.duration = 1

        maskLayer.add(animation, forKey: "makeRoundedCorners")
        self.layer.mask = maskLayer
    }
}

extension UIBezierPath {
    convenience init(roundedRect rect: CGRect, topLeftRadius r1: CGFloat, topRightRadius r2: CGFloat, bottomRightRadius r3: CGFloat, bottomLeftRadius r4: CGFloat) {
        let left  = CGFloat(Double.pi)
        let up    = CGFloat(1.5*Double.pi)
        let down  = CGFloat(Double.pi / 2)
        let right = CGFloat(0.0)
        self.init()

        addArc(withCenter: CGPoint(x: rect.minX + r1, y: rect.minY + r1), radius: r1, startAngle: left,  endAngle: up,    clockwise: true)
        addArc(withCenter: CGPoint(x: rect.maxX - r2, y: rect.minY + r2), radius: r2, startAngle: up,    endAngle: right, clockwise: true)
        addArc(withCenter: CGPoint(x: rect.maxX - r3, y: rect.maxY - r3), radius: r3, startAngle: right, endAngle: down,  clockwise: true)
        addArc(withCenter: CGPoint(x: rect.minX + r4, y: rect.maxY - r4), radius: r4, startAngle: down,  endAngle: left,  clockwise: true)
        close()
    }
}

So I wrote a custom UIBeziePath Initializer and then added an extension for UIView as described.

But when I'm trying to update the state of the cell, nothing happens, it's just draw the path instantly. What should I do with that?

I attached some pictures to give an idea what's going on

enter image description here enter image description here

I got my mistake and replaced the initial path with maskLayer.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 5).cgPath

But now this thing is happening

enter image description here


Solution

  • The reasons why the animation happens immediately is that the same path is assigned to maskLayer and is used as the toValue of the animation.

    let maskLayer = CAShapeLayer()
    maskLayer.path = path.cgPath // same path used here...
    
    let animation = CABasicAnimation(keyPath: "path")
    animation.toValue = path.cgPath // ... as here
    animation.duration = 1
    
    maskLayer.add(animation, forKey: "makeRoundedCorners")
    self.layer.mask = maskLayer
    

    Since that path is the expected end value of your animation, you need to provide a value to animate from.

    Note: the path animation is "undefined" if the two paths have a different number of control points or segment.