Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animated curve line in Swift 3

I want to draw some bezier lines and I want to animate them with a wave effect,

Example enter image description here

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


like image 385
Badr Filali Avatar asked Nov 27 '22 01:11

Badr Filali


1 Answers

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:

enter image description here

like image 62
Rob Avatar answered Dec 05 '22 13:12

Rob