2
votes

Is there a way to animate the stroke drawing of a path whenever its destination point changes?

Here is a snippet of my code:

struct JourneyMapLineView: View {        
    @Binding var rects: [CGRect]

    var body: some View {
        rects.count != 0 ?
            JourneyMapLineShape(startRect: rects[0], endRect: rects[rects.count-1])
                .stroke(Color.red), lineWidth: 8)
                .animation(.easeInOut(duration: 0.3)) //<-- Is not working
        : nil
    }
}

struct JourneyMapLineShape: Shape {
    var startRect: CGRect
    var endRect: CGRect

    func path(in _: CGRect) -> Path {
        var path = Path()

        path.move(to: startRect.origin)
        path.addLine(to: endRect.origin)

        return path
    }
}

Currently, as you can see, there is no animation by changing the value of endRect:

enter image description here

I have already looked at some similar questions, it seems that this is a new case,.

Thank you so much!

1
can you also add the part of code from where you are initializing JourneyMapLineView.user832

1 Answers

1
votes

Here is a demo of possible solution. Tested with Xcode 11.4 / iOS 13.4

demo

// Route shape animating head point
struct Route: Shape {
    var points: [CGPoint]
    var head: CGPoint

    // make route animatable head position only
    var animatableData: AnimatablePair<CGFloat, CGFloat> {
        get { AnimatablePair(head.x, head.y) }
        set {
            head.x = newValue.first
            head.y = newValue.second
        }
    }

    func path(in rect: CGRect) -> Path {
        Path { path in
            guard points.count > 1 else { return }
            path.move(to: points.first!)
            _ = points.dropFirst().dropLast().map { path.addLine(to: $0) }
            path.addLine(to: head)
        }
    }
}

// Route view model holding all points and notifying when last one changed
class RouteVM: ObservableObject {
    var points = [CGPoint.zero] {
        didSet {
            self.lastPoint = points.last ?? CGPoint.zero
        }
    }
    @Published var lastPoint = CGPoint.zero
}

struct DemoRouteView: View {
    @ObservedObject var vm = RouteVM()

    var body: some View {
        GeometryReader { gp in
            ZStack { // area
                Rectangle().fill(Color.white)
                    .gesture(DragGesture(minimumDistance: 0).onEnded { value in
                        self.vm.points.append(value.location) // read coordinates in area
                    })

                Circle().fill(Color.blue).frame(width: 20)
                    .position(self.vm.points.first!) // show initial point

                // draw route when head changed, animating
                Route(points: self.vm.points, head: self.vm.lastPoint)
                    .stroke(style: StrokeStyle(lineWidth: 8, lineCap: .round, lineJoin: .miter, miterLimit: 0, dash: [], dashPhase: 0))
                    .foregroundColor(.red)
                    .animation(.linear(duration: 0.5))
            }
            .onAppear {
                let area = gp.frame(in: .global)
                // just initail point at the bottom of screen
                self.vm.points = [CGPoint(x: area.midX, y: area.maxY)]
            }
        }.edgesIgnoringSafeArea(.all)
    }
}