Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate an expanding drawing in iOS UIView Swift

I would like to create a custom dynamic drawing inside a UIView using a CGContext so that when I execute a UIView animation on it the drawing will be rendered during animation/interpolation. I tried using a custom CALayer and override its draw method but that fails. I tried subclassing a UIView draw(_ in:CGRect) but that gets drawn only first time. This is what I want to do:

class RadioWaveView : UIView {
    override func draw(_ in:CGRect) {
        // draw something, for example an ellipse based on frame size, something like this:
        // let context = CGGraphicsCurrentContext()
        // context.addEllipse(self.frame)
        // Unfortunately this method is called only once, not during animation
    }
}

in viewDidAppear():

UIView.animate(withDuration: 5, delay: 1, options: [.repeat], animations: {
        self.myRadioWaveView.frame = CGRect(x:100,y:100,width:200,heigh:300)
    }, completion: { flag in
    })

The animation works because I set the background color of myRadioWaveView and see it expand on screen. Placing a breakpoint in draw shows that it is executed only once.

EDIT: In essence, I am asking this question: How can we create a custom UIView and use UIView.animate() to animate that view's rendering? Let's keep the UIView.animate() method, and how can we animate an expanding circle (or any other custom drawing within a UIView) ?

EDIT: Here is a custom class I created to draw just a triangle. When I animate its bounds, the triangle scales with the underlying image of the view but the draw method is not called during the animation. I don't want that; I want the draw() method to redraw the drawing every frame of the animation.

open class TriangleView : UIView {

override open func draw(_ rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()!
    let path = CGMutablePath()
    path.move(to: CGPoint.zero)
    path.addLine(to: CGPoint(x:self.frame.width,y:0))
    path.addLine(to: CGPoint(x:self.frame.width/2,y:self.frame.height))
    path.addLine(to: CGPoint.zero)
    path.closeSubpath()
    context.beginPath()
    context.setStrokeColor(UIColor.white.cgColor)
    context.setLineWidth(3)
    context.addPath(path)
    context.closePath()
    context.strokePath()
}

}
like image 574
andrewz Avatar asked Jul 22 '17 21:07

andrewz


People also ask

How does UIView animate work?

it queries the views objects for changed state and gets back the initial and target values and hence knows what properties to change and in what range to perform the changes. it calculates the intermediate frames, based on the duration and initial/target values, and fires the animation.

Does UIView animate need weak self?

No, it is not needed in this case. animations and completion are not retained by self so there is no risk of strong retain cycle.

Is UIView animate asynchronous?

UIView. animate runs on the main thread and is asynchronous.

How do you animate a view in iOS Swift?

To be exact, whenever you want to animate the view, you actually call layoutIfNeeded on the superview of that view. Try this instead: UIView. animate(withDuration: 0.1, delay: 0.1, options: UIViewAnimationOptions.


1 Answers

I had been working in your question, this approach is using Using CABasicAnimation and CAShapeLayer I had defined a custom UIView Class to make the work, I will add further improvements @IBDesignable and @IBInspectables

import UIKit

class RadioWaveAnimationView: UIView {

    var animatableLayer : CAShapeLayer?

    override func awakeFromNib() {
        super.awakeFromNib()
        self.layer.cornerRadius = self.bounds.height/2

        self.animatableLayer = CAShapeLayer()
        self.animatableLayer?.fillColor = self.backgroundColor?.cgColor
        self.animatableLayer?.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
        self.animatableLayer?.frame = self.bounds
        self.animatableLayer?.cornerRadius = self.bounds.height/2
        self.animatableLayer?.masksToBounds = true
        self.layer.addSublayer(self.animatableLayer!)
        self.startAnimation()
    }


    func startAnimation()
    {
        let layerAnimation = CABasicAnimation(keyPath: "transform.scale")
        layerAnimation.fromValue = 1
        layerAnimation.toValue = 3
        layerAnimation.isAdditive = false

        let layerAnimation2 = CABasicAnimation(keyPath: "opacity")
        layerAnimation2.fromValue = 1
        layerAnimation2.toValue = 0
        layerAnimation2.isAdditive = false

        let groupAnimation = CAAnimationGroup()
        groupAnimation.animations = [layerAnimation,layerAnimation2]
        groupAnimation.duration = CFTimeInterval(2)
        groupAnimation.fillMode = kCAFillModeForwards
        groupAnimation.isRemovedOnCompletion = true
        groupAnimation.repeatCount = .infinity

        self.animatableLayer?.add(groupAnimation, forKey: "growingAnimation")
    }
    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */

}

Results

enter image description here

Hope this helps

like image 153
Reinier Melian Avatar answered Oct 09 '22 17:10

Reinier Melian