Search code examples
animationswiftuipathshapes

SwiftUI animations of multiple shapes not working correctly


I'm trying to show different shapes as path animation after each other. The animation should start always from showing nothing to the finished shape. My problem is that it's working just with the first shape, I think because of the onAppear. However, after clicking the next button, the next shapes are not animating. I tried playing with the pathProgress value, and another boolean, but either it doesn't animate at all, or it animates backwards.

import SwiftUI

struct ContentView: View {
    @State var id = 1
    @State var pathProgress = 0.0
    
    var body: some View {
        VStack {
            ConsonantDrawing(id: id)
                .trim(from: 0, to: pathProgress)
                .stroke(Color.red, style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
                .animation(.linear(duration: 4.0), value: pathProgress)
                .onAppear {
                    pathProgress = 1.0
                }
                .frame(width: UIScreen.main.bounds.width * 0.5, height: UIScreen.main.bounds.height * 0.5)
            
            Spacer()
            
            HStack {
                Button {
                    id -= 1
                } label: {
                    Image(systemName: "arrow.backward.circle")
                }
                
                Spacer()
                
                Button {
                    id += 1
                } label: {
                    Image(systemName: "arrow.forward.circle")
                }
            }
            .padding()
            .font(.largeTitle)
        }
    }
}

struct ConsonantDrawing: Shape {
    var id: Int
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let width = rect.size.width
        let height = rect.size.height
        
        switch id {
        case 1:
            path.move(to: CGPoint(x: 0.12069*width, y: 0.97451*height))
            path.addLine(to: CGPoint(x: 0.12069*width, y: 0.52102*height))
            path.addCurve(to: CGPoint(x: 0.36207*width, y: 0.33497*height), control1: CGPoint(x: 0.12069*width, y: 0.38149*height), control2: CGPoint(x: 0.36207*width, y: 0.33497*height))
            path.addCurve(to: CGPoint(x: 0.03448*width, y: 0.27684*height), control1: CGPoint(x: 0.2069*width, y: 0.28846*height), control2: CGPoint(x: 0.03448*width, y: 0.27684*height))
            path.addCurve(to: CGPoint(x: 0.98276*width, y: 0.27684*height), control1: CGPoint(x: 0.03448*width, y: -0.02549*height), control2: CGPoint(x: 0.94828*width, y: -0.09526*height))
            path.addLine(to: CGPoint(x: 0.98276*width, y: 0.97451*height))
            
        case 2:
            path.move(to: CGPoint(x: 0.04412*width, y: 0.24668*height))
            path.addCurve(to: CGPoint(x: 0.27941*width, y: 0.19048*height), control1: CGPoint(x: 0.08154*width, y: 0.17525*height), control2: CGPoint(x: 0.20588*width, y: 0.15476*height))
            path.addCurve(to: CGPoint(x: 0.35294*width, y: 0.34524*height), control1: CGPoint(x: 0.35294*width, y: 0.22619*height), control2: CGPoint(x: 0.38079*width, y: 0.28888*height))
            path.addCurve(to: CGPoint(x: 0.20588*width, y: 0.42857*height), control1: CGPoint(x: 0.32353*width, y: 0.40476*height), control2: CGPoint(x: 0.26471*width, y: 0.42857*height))
            path.addCurve(to: CGPoint(x: 0.04412*width, y: 0.32143*height), control1: CGPoint(x: 0.14706*width, y: 0.42857*height), control2: CGPoint(x: 0.05882*width, y: 0.40476*height))
            path.addCurve(to: CGPoint(x: 0.07353*width, y: 0.15476*height), control1: CGPoint(x: 0.02941*width, y: 0.2381*height), control2: CGPoint(x: 0.05588*width, y: 0.19048*height))
            path.addCurve(to: CGPoint(x: 0.35294*width, y: 0.04762*height), control1: CGPoint(x: 0.10294*width, y: 0.09524*height), control2: CGPoint(x: 0.23438*width, y: 0.04762*height))
            path.addCurve(to: CGPoint(x: 0.63235*width, y: 0.19048*height), control1: CGPoint(x: 0.47059*width, y: 0.04762*height), control2: CGPoint(x: 0.60294*width, y: 0.08333*height))
            path.addCurve(to: CGPoint(x: 0.57353*width, y: 0.39286*height), control1: CGPoint(x: 0.65523*width, y: 0.27381*height), control2: CGPoint(x: 0.61001*width, y: 0.34856*height))
            path.addCurve(to: CGPoint(x: 0.47059*width, y: 0.58333*height), control1: CGPoint(x: 0.51471*width, y: 0.46429*height), control2: CGPoint(x: 0.47059*width, y: 0.53571*height))
            path.addCurve(to: CGPoint(x: 0.47059*width, y: 0.83333*height), control1: CGPoint(x: 0.47059*width, y: 0.62143*height), control2: CGPoint(x: 0.47059*width, y: 0.78571*height))
            path.addCurve(to: CGPoint(x: 0.98529*width, y: 0.83333*height), control1: CGPoint(x: 0.47059*width, y: 1.02381*height), control2: CGPoint(x: 0.95588*width, y: 1.04762*height))
            path.addCurve(to: CGPoint(x: 0.98529*width, y: 0.0119*height), control1: CGPoint(x: 0.98529*width, y: 0.60714*height), control2: CGPoint(x: 0.98529*width, y: 0.19048*height))
            
        case 3:
            path.move(to: CGPoint(x: 0.03186*width, y: 0.38372*height))
            path.addCurve(to: CGPoint(x: 0.27056*width, y: 0.27907*height), control1: CGPoint(x: 0.04505*width, y: 0.25581*height), control2: CGPoint(x: 0.19913*width, y: 0.25581*height))
            path.addCurve(to: CGPoint(x: 0.34199*width, y: 0.44186*height), control1: CGPoint(x: 0.34199*width, y: 0.30233*height), control2: CGPoint(x: 0.36904*width, y: 0.38681*height))
            path.addCurve(to: CGPoint(x: 0.17056*width, y: 0.51163*height), control1: CGPoint(x: 0.31341*width, y: 0.5*height), control2: CGPoint(x: 0.24198*width, y: 0.52326*height))
            path.addCurve(to: CGPoint(x: 0.03186*width, y: 0.38372*height), control1: CGPoint(x: 0.09913*width, y: 0.5*height), control2: CGPoint(x: 0.04615*width, y: 0.46512*height))
            path.closeSubpath()
            path.move(to: CGPoint(x: 0.03186*width, y: 0.38372*height))
            path.addCurve(to: CGPoint(x: 0.07056*width, y: 0.19767*height), control1: CGPoint(x: 0.01758*width, y: 0.30233*height), control2: CGPoint(x: 0.05341*width, y: 0.23256*height))
            path.addCurve(to: CGPoint(x: 0.24198*width, y: 0.05814*height), control1: CGPoint(x: 0.09913*width, y: 0.13953*height), control2: CGPoint(x: 0.17056*width, y: 0.0814*height))
            path.addCurve(to: CGPoint(x: 0.37056*width, y: 0.15116*height), control1: CGPoint(x: 0.29913*width, y: 0.09302*height), control2: CGPoint(x: 0.24198*width, y: 0.05814*height))
            path.addCurve(to: CGPoint(x: 0.49913*width, y: 0.05814*height), control1: CGPoint(x: 0.49913*width, y: 0.05814*height), control2: CGPoint(x: 0.41341*width, y: 0.11628*height))
            path.addCurve(to: CGPoint(x: 0.65627*width, y: 0.19767*height), control1: CGPoint(x: 0.61341*width, y: 0.09302*height), control2: CGPoint(x: 0.64413*width, y: 0.1532*height))
            path.addCurve(to: CGPoint(x: 0.54615*width, y: 0.47674*height), control1: CGPoint(x: 0.67849*width, y: 0.27907*height), control2: CGPoint(x: 0.58159*width, y: 0.43348*height))
            path.addCurve(to: CGPoint(x: 0.48484*width, y: 0.62791*height), control1: CGPoint(x: 0.5277*width, y: 0.49927*height), control2: CGPoint(x: 0.48484*width, y: 0.5814*height))
            path.addCurve(to: CGPoint(x: 0.48484*width, y: 0.81395*height), control1: CGPoint(x: 0.48484*width, y: 0.66512*height), control2: CGPoint(x: 0.48484*width, y: 0.76744*height))
            path.addCurve(to: CGPoint(x: 0.98484*width, y: 0.81395*height), control1: CGPoint(x: 0.48484*width, y: 1.02326*height), control2: CGPoint(x: 0.95627*width, y: 1.02326*height))
            path.addCurve(to: CGPoint(x: 0.98484*width, y: 0.02326*height), control1: CGPoint(x: 0.98484*width, y: 0.59302*height), control2: CGPoint(x: 0.98484*width, y: 0.19767*height))
            
        default:
            path.move(to: CGPoint(x: 0.12069*width, y: 0.97451*height))
            path.addLine(to: CGPoint(x: 0.12069*width, y: 0.52102*height))
        }
        
        return path
    }
}

Solution

  • The issue is that you are not resetting pathProgress. What I did was to reset it when pressing the back or next buttons and checking wheter the current id was the first or the last one to avoid issue with animations. I also removed the .animation modifier on the ConsonantDrawing view because it was causing issues and placed the animtion in the actions rather:

    struct ContentView: View {
        
        @State var id = 1
        @State var pathProgress = 0.0
        
        var body: some View {
            VStack {
                ConsonantDrawing(id: id)
                    .trim(from: 0, to: pathProgress)
                    .stroke(Color.red, style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
                    /// Remove this .animation line because is part of the issue!
                    //.animation(.linear(duration: 4.0), value: pathProgress)
                    .frame(width: UIScreen.main.bounds.width * 0.5, height: UIScreen.main.bounds.height * 0.5)
                
                Spacer()
                
                HStack {
                    Button {
                        guard id != 1 else { return }
                        id -= 1
                        pathProgress = 0
                        withAnimation(.linear(duration: 4)) {
                            pathProgress = 1.0
                        }
                    } label: {
                        Image(systemName: "arrow.backward.circle")
                    }
                    
                    Spacer()
                    
                    Button {
                        guard id != 3 else { return }
                        pathProgress = 0
                        id += 1
                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                            withAnimation(.linear(duration: 4)) {
                                pathProgress = 1.0
                            }
                        }
                    } label: {
                        Image(systemName: "arrow.forward.circle")
                    }
                }
                .padding()
                .font(.largeTitle)
            }
            .onAppear {
                print("On appear")
                withAnimation(.linear(duration: 4)) {
                    pathProgress = 1.0
                }
            }
        }
    }
    

    Here's the result:

    Consonant Drawing

    Let me know how'd you find this soltution!