Search code examples
iosswiftanimationuiviewcalayer

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()
}

}

Solution

  • Use this simplest way

    Full example code

    import UIKit
    
    class ViewController: UIViewController {
        
        
        @IBOutlet weak var animatableView: UIView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
    
            //Circle
            //animatableView.layer.cornerRadius = animatableView.bounds.size.height/2
            //animatableView.layer.masksToBounds = true
            //triangle
            self.applyTriangle(givenView: animatableView)
            
            UIView.animate(withDuration: 2, delay: 1, options: [.repeat], animations: {
                self.animatableView.transform = CGAffineTransform(scaleX: 2, y: 2)
            }) { (finished) in
                self.animatableView.transform = CGAffineTransform.identity
            }
            
        }
        
        func applyTriangle(givenView: UIView){
            
            let bezierPath = UIBezierPath()
            bezierPath.move(to: CGPoint(x: givenView.bounds.width / 2, y: givenView.bounds.width))
            bezierPath.addLine(to: CGPoint(x: 0, y: 0))
            bezierPath.addLine(to: CGPoint(x: givenView.bounds.width, y: 0))
            bezierPath.close()
            
            let shapeLayer = CAShapeLayer(layer: givenView.layer)
            shapeLayer.path = bezierPath.cgPath
            shapeLayer.frame = givenView.bounds
            shapeLayer.masksToBounds = true
            givenView.layer.mask = shapeLayer
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
    
    }
    

    Results

    enter image description here

    Hope this helps you