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()
}
}
Use this simplest way
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
Hope this helps you