Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI | Animate This Path Shape

I have the following case:

I want to flip this shape with a custom animation. I don't know how to describe it properly.

Whenever I tap on the arrow, it should transform into the other one. Without flipping, rotating, etc. just simply transforming.

If that's not precise enough, feel free to comment!

Demo Code

struct ContentView: View {
    
    @State private var change = false
    
    private let arrowWidth: CGFloat = 80
    
    
    var body: some View {
        Path { path in
            path.move(to: .zero)
            path.addLine(to: CGPoint(x: arrowWidth/2, y: change ? -20 : 20))
            path.move(to: CGPoint(x: arrowWidth/2, y: change ? -20 : 20))
            path.addLine(to: CGPoint(x: arrowWidth, y: 0))
        }
        .stroke(style: StrokeStyle(lineWidth: 12, lineCap: .round))
        .frame(width: arrowWidth)
        .foregroundColor(.green)
        .animation(.default)
        .onTapGesture { change.toggle() }
        .padding(.top, 300)
    }
}

As you can see now it has no animation. I have no idea how to do it.

Any help is much appreciated. Thanks!

Note: I can't do this with two rounded rectangles + simply rotating, since I have an opacity animation, which makes the overlapping visible.

like image 907
Mofawaw Avatar asked Jan 24 '23 16:01

Mofawaw


1 Answers

Here is possible approach - move path into custom shape and make changed parameter as animatable property.

Update: Re-tested with Xcode 13.3 / iOS 15.4

demo

struct MyArrow: Shape {
    var width: CGFloat
    var offset: CGFloat
    
    var animatableData: CGFloat {
        get { offset }
        set { offset = newValue }
    }
    
    func path(in rect: CGRect) -> Path {
        Path { path in
            path.move(to: .zero)
            path.addLine(to: CGPoint(x: width/2, y: offset))
            path.move(to: CGPoint(x: width/2, y: offset))
            path.addLine(to: CGPoint(x: width, y: 0))
        }
    }
}

struct ContentView: View {
    
    @State private var change = false
    
    private let arrowWidth: CGFloat = 80
    
    
    var body: some View {
        MyArrow(width: arrowWidth, offset: change ? -20 : 20)
        .stroke(style: StrokeStyle(lineWidth: 12, lineCap: .round))
        .frame(width: arrowWidth)
        .foregroundColor(.green)
        .contentShape(Rectangle())
        .onTapGesture { withAnimation { change.toggle() } }
        .onTapGesture { change.toggle() }
        .padding(.top, 300)
    }
}
like image 133
Asperi Avatar answered Feb 06 '23 02:02

Asperi