Search code examples
swiftalgorithmswiftuipathbezier

Randomly Generated Curved Lines in Swift UI


I'm trying to create randomly generated wavy lines using Swift UI. I tried copying the math from this question on SO.

https://stackoverflow.com/a/54051986/1895804

But my bezier curves are creating sharp points instead of curving smoothly. Does anyone know why this is happening?

It should look like this:

the correct output

But it looks like this with jagged points. example project showing the curved lines issue

struct SuperCurvyLine: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let canvasW = rect.width
        let canvasH = rect.height
        let canvasX = canvasW/2
        let canvasY = canvasH/2
        let angVal = 0.20
        let tension = 1.0
        let npts: CGFloat = 60
        var dw = [Double]()
        var xs = [Double]()
        var ys = [Double]()
        var vys = [Double]()
        var vxs = [Double]()
        
        xs.append(0)
        ys.append(canvasY)
        var angle = 0.0
        for i in 0...Int(npts) {
            dw.append(2 * Double.random(in: 0...1) - 1)
            vxs.append(10 * cos(2 * Double.pi * angle))
            vys.append(10 * sin(2 * Double.pi * angle))
            angle = angle + dw[i] * angVal
        }
        
        for i in 1...Int(npts) {
            xs.append(xs[i - 1] + 3 * (vxs[i - 1] + vxs[i]) / 2)
            ys.append(ys[i - 1] + 3 * (vys[i - 1] + vys[i]) / 2)
        }
        
        path.move(to: CGPoint(x: xs[0], y: ys[0]))
        
        for i in 1...Int(npts) {
            let cp1x = xs[i - 1] + tension * vxs[i - 1]
            let cp1y = ys[i - 1] + tension * vys[i - 1]
            let cp2x = xs[i] - tension * vxs[i]
            let cp2y = ys[i] - tension * vys[i]
            let to = CGPoint(x: cp1x, y: cp1y)
            let control1 = CGPoint(x: cp2x, y: cp2y)
            let control2 = CGPoint(x: xs[i], y: ys[i])
            path.addCurve(to: control1, control1: to, control2: control1)
        }
        
        return path
    }
}

Solution

  • You mixed up the to, control1 and control2 points. According to the original JS, they should be:

    path.addCurve(to: control2, control1: to, control2: control1)
    

    You have named these rather confusing names. It should really be:

    let control1 = CGPoint(x: cp1x, y: cp1y)
    let control2 = CGPoint(x: cp2x, y: cp2y)
    let to = CGPoint(x: xs[i], y: ys[i])
    path.addCurve(to: to, control1: control1, control2: control2)
    

    Now it makes a lot of sense. Note that the order of parameters in JS is "control1, control2, to", different from the Swift API.

    For even smoother lines, you can use a lineJoin: .round stroke style:

    .stroke(style: .init(lineJoin: .round))