在 SwiftUI 视图上为 DragGesture 的 predictedEndTranslation 设置动画

Animating to the predictedEndTranslation of a DragGesture on a SwiftUI view

我想在 DragGesture 的预测位置放置一个视图,这就是我在 .gestureEnded 闭包中所做的,将更改包装在 withAnimation 块中。然而,当我在实时视图中尝试时,变化不是动画的。

这是框架的错误还是我做错了什么?

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()
    }
}

好像translateEffect不能做动画。这是有道理的,因为 CGAffineTransform 不符合 Animatable,所以这可能是预期的。幸运的是,你仍然可以使用 .offset(x: 0, y: ty).

这对你有用吗?

请注意,某些动画(例如这个动画)在 Xcode 实时预览中不起作用。您需要在设备或模拟器上 运行 它。

SwiftUI/iOS 13.4 效果的示例实现,还修复了重复拖动时视图跳动的问题。

诀窍是使用 @GestureState 跟踪正在进行的手势并单独保持整体状态变化,使用 .offset 修饰符应用变化:

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