简体   繁体   中英

Animating to the predictedEndTranslation of a DragGesture on a SwiftUI view

I want to place a view at the predicted location of a DragGesture , that's what I do in my .gestureEnded closure, wrapping the change in a withAnimation block. Yet, when I try it in the live view, the change isn't animated.

Is this a bug of the framework or am I doing something wrong?

struct ContentView: View {

    @State var ty: CGFloat = 0

    var dragGesture: some Gesture {
        DragGesture()
            .onChanged { theGesture in
                self.ty = theGesture.translation.height
                print("Changed")
            }
            .onEnded { theGesture in
                print("Ended")
                withAnimation(Animation.easeOut(duration: 3)) {
                    self.ty = theGesture.predictedEndTranslation.height
                }
            }
    }

    var body: some View {
        VStack {
            ForEach(1 ..< 5) { _ in

                Color.red
                    .frame(minHeight: 20, maxHeight: 100)
                    .padding(0)

            }
        }
        .transformEffect(.init(translationX: 0, y: ty))
        .gesture(dragGesture)
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

It seems translateEffect cannot be animated. It kind of makes sense, since CGAffineTransform does not conform to Animatable , so that might be as intended. Fortunately, you can still use .offset(x: 0, y: ty) .

Will that work for you?

Note that some animations (like this one), do not work in Xcode Live Preview. You need to run it on a device or the simulator.

An example implementation of the effect for SwiftUI/iOS 13.4, also fixing the view jumping around when dragged repeatedly.

The trick is to use @GestureState to keep track of the gesture in progress and to keep the overall state change separately, applying the changes with the .offset modifier:

struct DragGestureView: View {

    @GestureState var dragOffset = CGSize.zero
    @State var offset: CGFloat = 0

    var dragGesture: some Gesture {
        DragGesture()
            .updating($dragOffset) { value, state, _ in
                state = value.translation
            }
            .onEnded { gesture in
                // keep the offset already moved without animation
                self.offset += gesture.translation.height
                withAnimation(Animation.easeOut(duration: 3)) {
                    self.offset += gesture.predictedEndTranslation.height - gesture.translation.height
                }
            }
    }

    var body: some View {
        VStack {
            ForEach(1 ..< 5) { _ in
                Color.red
                    .frame(minHeight: 20, maxHeight: 100)
            }
        }
        .offset(x: 0, y: offset + dragOffset.height)
        .gesture(dragGesture)
    }

}

struct DragGestureView_Previews: PreviewProvider {
    static var previews: some View {
        DragGestureView()
    }
}

Runnable example via Github: SwiftUIPlayground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM