问题
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 best method to do it ? I found only 2 libs for this, but they are not really useful for what I need, I try to modify the code of one lib, unfortunately without success https://github.com/yourtion/YXWaveView
I found this lib, https://antiguab.github.io/bafluidview/ which does the work, but it written in obj-c, maybe you know something like this in swift
回答1:
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 var displayLink: CADisplayLink?
private var startTime: CFAbsoluteTime?
/// The `CAShapeLayer` that will contain the animated path
private let shapeLayer: CAShapeLayer = {
let _layer = CAShapeLayer()
_layer.strokeColor = UIColor.white.cgColor
_layer.fillColor = UIColor.clear.cgColor
_layer.lineWidth = 3
return _layer
}()
// start the display link when the view appears
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
view.layer.addSublayer(shapeLayer)
self.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) {
stopDisplayLink()
}
/// Start the display link
private func startDisplayLink() {
startTime = CFAbsoluteTimeGetCurrent()
displayLink?.invalidate()
displayLink = CADisplayLink(target: self, selector:#selector(handleDisplayLink(_:)))
displayLink?.add(to: RunLoop.current, forMode: .commonModes)
}
/// Stop the display link
private func stopDisplayLink() {
displayLink?.invalidate()
displayLink = nil
}
/// Handle the display link timer.
///
/// - Parameter displayLink: The display link.
func handleDisplayLink(_ displayLink: CADisplayLink) {
let elapsed = CFAbsoluteTimeGetCurrent() - 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 centerY = view.bounds.height / 2
let amplitude = CGFloat(50) - fabs(fmod(CGFloat(elapsed), 3) - 1.5) * 40
func f(_ x: Int) -> CGFloat {
return sin(((CGFloat(x) / view.bounds.width) + CGFloat(elapsed)) * 4 * .pi) * amplitude + centerY
}
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: f(0)))
for x in stride(from: 0, to: Int(view.bounds.width + 9), by: 10) {
path.addLine(to: CGPoint(x: CGFloat(x), 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:
来源:https://stackoverflow.com/questions/44006942/animated-curve-line-in-swift-3