How to switch views in SwiftUI interactively based on a pan gesture or scroll view position?

Viewed 289

In an UIKit app I've built I used to have this effect where a scroll view would control the animation of enabling/disabling specific constraints in a view by using an UIPropertyAnimator and manipulating it's fractionComplete property. This allowed the animation to follow the scroll view's progression.

In SwiftUI I do not control constraints anymore but rather I change the whole view hierarchy based on a @State variable (for eg, the view I want to transition between 2 states has a property called isExpanded).

This simple transition is easily done with SwiftUI's withAnimation by simply toggling the bool value that I need. Using the matchedGeometryEffect(id:in:properties:anchor:isSource:) I can easily have elements move from/to the correct positions.

What I am trying to figure out is how can I basically pause and then control the progression of the animation so that the transition between my start and end state is controlled via a pan gesture or, like I used to do it in UIKit, via a ScrollView.

Here's a basic example of my SwiftUI code:

struct MyTwoStatesView: View {
    
    @Namespace var namespace
    @State var isExpanded: Bool = true
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 4)
                .fill(Color.gray)
            if isExpanded {
                ExpandedStateView(namespace: namespace)
            }
            else {
                CollapsedStateView(namespace: namespace)
            }
        }
        .fixedSize(horizontal: false, vertical: true)
        .onTapGesture {
            withAnimation {
                isExpanded.toggle()
            }
        }
    }
}

And here's the effect that the final animation has: enter image description here


Later edit: Here's a gist for the "full" example: https://gist.github.com/zeusent/39f58827519d9bfda63271a9cb24d6b6

1 Answers

Use a timer.

Start the timer at the beginning of the pan gesture.

While the timer is running, check for or respond to the pan gesture.

Get the x and y coordinates of the pan gesture to determine the speed of the pan gesture per time and use that to decide the increments per time that the progression of the animation happens.

End the timer at the end of the pan gesture or at the full maximizing or full minimizing.

"And now for something entirely different."

The Add a comment is not working for me. I click it and it takes me to the top of the page and I cannot input any text.

There was a comment after my post that said, "increments per time that the progression of the animation happens." how do you control this though? – aheze 17 mins ago Here is the answer to that since I can not use Add a comment:

Get a timer running, and maybe each 1/100 second get the x and y coordinates of the pan gesture. If the gesture is moving slow, then resize slow in increments. Resize to some size that attempts to keep the x and y of the original beginning gesture under the pan gesture.

If the user pan gestures fast, then resize fast. If they resize slowly, and it takes a full one second for the gesture to complete, then resize at size minus 1/100 the original (or whatever your timer is set at).

"To pause" the resizing: if the user gestures a partial pan then with the timer running, it should detect that the pan gesture has paused, then it has not input to tell it to continue the resizing.

I hope you understand that as I might not be back to this question before the time to answer it runs out.

Related