Updating a path before Animating it in SwiftUI

Viewed 65

I am trying to animate my path with a move value, It works almost, but the needed update to path happens after animation is done! Which must happen before Animation! How can I make it possible?

PS: The issue is about Path, and I am trying solve it through path.

struct HatchedShap: Shape {
    
    let dis: CGFloat
    var move: CGFloat
    
    var animatableData: CGFloat {
        get { return move }
        set { move = newValue }
    }

    func path(in rect: CGRect) -> Path {
        return Path { path in

            for index in -Int(move*2.0)...Int((rect.height)/dis) {
                
                path.move(to: CGPoint(x: rect.minX, y: rect.minY + (CGFloat(index) + move)*dis))
                path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + (CGFloat(index) + move)*dis))
                
            }
            
        }
    }
    
}

use case:

struct ContentView: View {
    
    @State private var move: CGFloat = CGFloat()

    var body: some View {

        HatchedShap(dis: 20.0, move: move)
            .stroke(Color.red, style: StrokeStyle(lineWidth: 2.0))
            .background(Color.black)
            .frame(width: 100, height: 200)
            .onTapGesture {
  
                if (move == -0.5) { move = 0}
                else { move = -0.5 }
            }
            .animation(Animation.linear(duration: 1.0), value: move)

    }
    
}

enter image description here

2 Answers

In your path code, assign y to a let constant and check to make sure it is larger than 0 before adding the line to the path:

struct HatchedShap: Shape {
    
    let dis: CGFloat
    var move: CGFloat
    
    var animatableData: CGFloat {
        get { return move }
        set { move = newValue }
    }

    func path(in rect: CGRect) -> Path {
        return Path { path in

            for index in -Int(move*2.0)...Int((rect.height)/dis) {
                let y = rect.minY + (CGFloat(index) + move)*dis
                if y > 0 {
                    path.move(to: CGPoint(x: rect.minX, y: y))
                    path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + (CGFloat(index) + move)*dis))
                }
                
            }
            
        }
    }
    
}

I think using stride(from:through:by:) is much easier. You can then continue with the loop if it is out of the range of the rect. This example also nicely wraps around if the factor's absolute value is greater than 1.

Code:

struct HatchedShap: Shape {
    let dis: CGFloat
    var move: CGFloat

    var animatableData: CGFloat {
        get { return move }
        set { move = newValue }
    }

    func path(in rect: CGRect) -> Path {
        Path { path in
            for start in stride(from: 0, through: rect.height, by: dis) {
                let offset = start + dis * move.truncatingRemainder(dividingBy: 1)
                guard 0 ... rect.height ~= offset else {
                    continue
                }

                path.move(to: CGPoint(x: rect.minX, y: rect.minY + offset))
                path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + offset))
            }
        }
    }
}

Results:

Factor of -0.5 Factor of -2
Factor of -0.5 Factor of -2

Small note:

You can simplify:

if (move == -0.5) {
    move = 0
} else {
    move = -0.5
}

To the following, which removes duplication & possibly faster because there is no branching (depends on if the compiler optimizes this):

move = -0.5 - move
Related