I want to draw some bezier lines and I want to animate them with a wave effect,
Example
Do you have some ideas about how I can do this ? Bezier line is it the b
You can use a display link, a special kind of timer optimized for screen refresh rates, to change the path
that is being rendered. The handler for the display link should calculate the amount of time that has elapsed and modify the path to be rendered accordingly. You can either use a CAShapeLayer
to render the path, or you can use a custom UIView
subclass. The shape layer is probably easier:
class ViewController: UIViewController {
private weak var displayLink: CADisplayLink?
private var startTime: CFTimeInterval = 0
/// The `CAShapeLayer` that will contain the animated path
private let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 3
return shapeLayer
}()
// start the display link when the view appears
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.layer.addSublayer(shapeLayer)
startDisplayLink()
}
// Stop it when it disappears. Make sure to do this because the
// display link maintains strong reference to its `target` and
// we don't want strong reference cycle.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopDisplayLink()
}
/// Start the display link
private func startDisplayLink() {
startTime = CACurrentMediaTime()
self.displayLink?.invalidate()
let displayLink = CADisplayLink(target: self, selector:#selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
}
/// Stop the display link
private func stopDisplayLink() {
displayLink?.invalidate()
}
/// Handle the display link timer.
///
/// - Parameter displayLink: The display link.
@objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let elapsed = CACurrentMediaTime() - startTime
shapeLayer.path = wave(at: elapsed).cgPath
}
/// Create the wave at a given elapsed time.
///
/// You should customize this as you see fit.
///
/// - Parameter elapsed: How many seconds have elapsed.
/// - Returns: The `UIBezierPath` for a particular point of time.
private func wave(at elapsed: Double) -> UIBezierPath {
let elapsed = CGFloat(elapsed)
let centerY = view.bounds.midY
let amplitude = 50 - abs(elapsed.remainder(dividingBy: 3)) * 40
func f(_ x: CGFloat) -> CGFloat {
return sin((x + elapsed) * 4 * .pi) * amplitude + centerY
}
let path = UIBezierPath()
let steps = Int(view.bounds.width / 10)
path.move(to: CGPoint(x: 0, y: f(0)))
for step in 1 ... steps {
let x = CGFloat(step) / CGFloat(steps)
path.addLine(to: CGPoint(x: x * view.bounds.width, y: f(x)))
}
return path
}
}
The only tricky part is writing a wave
function that yields a UIBezierPath
for a particular time and yields the desired effect when you call it repeatedly as time passes. In this one, I'm rendering a sine curve, where the amplitude and the offset vary based upon the time that has elapsed at the point that the path is generated, but you can do whatever you want in your rendition. Hopefully this illustrates the basic idea.
The above code yields: