0

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

Max Kraev
  • 740
  • 12
  • 31

2 Answers2

1

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.

Vin Gazoil
  • 1,942
  • 2
  • 20
  • 24
  • Thank you! I'll definitely consider the possibility) Actually now strange thing happened. I've updated the question – Max Kraev Aug 20 '18 at 12:37
  • 1
    Luckily, I know that issue very well! This is because of the order of points or/and the number of points of the CGPath. You should try my extension, I took care of that particular problem. – Vin Gazoil Aug 20 '18 at 12:41
1

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.

David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
  • Actually, if you don't do that, at the end of the animation, the path of the maskLayer will be the same as before the animation. – Vin Gazoil Aug 20 '18 at 12:22
  • Correct. When the animation finishes and is removed the layer will present/show its model value (in this case: the value of the path property) again. I wasn't suggesting that the OP shouldn't assign the path to the layer. I was simply calling out that the implicit from value and explicit to value are both the same, which made the animation appear instant. – David Rönnqvist Aug 20 '18 at 12:30
  • Thank you! I got my mistake, but when I add a different path the strange thing occurs. I'll update my question in a minute – Max Kraev Aug 20 '18 at 12:32
  • @DavidRönnqvist Please check the updated question if you're available – Max Kraev Aug 20 '18 at 12:36
  • 1
    @MaxKraev Yes, as I mention towards the end of my answer: “Note that the path animation is "undefined" if the two paths have a different number of control points or segments.” – David Rönnqvist Aug 20 '18 at 12:39