Max Kraev
Max Kraev

Reputation: 770

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

Upvotes: 0

Views: 978

Answers (2)

Vin Gazoil
Vin Gazoil

Reputation: 2030

You need to specify the fromValue property of your animation object.
That way, Core Animation knows how to interpolate between the from and the to values.

let animation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.path))
animation.fromValue = UIBezierPath(…).path
animation.toValue = path.cgPath
animation.duration = 1

By the way, if you want to generate rectangles with nice rounded corners (a.k.a. squircles) with UIBezierPath, you may want to check my extension of UIBezierPath.

Upvotes: 1

David Rönnqvist
David Rönnqvist

Reputation: 56635

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.

Upvotes: 1

Related Questions