如何阻止 SwiftUI DragGesture 为子视图设置动画 [英] How to stop SwiftUI DragGesture from animating subviews

查看:29
本文介绍了如何阻止 SwiftUI DragGesture 为子视图设置动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个自定义模态,当我拖动模态时,任何附加了动画的子视图都会在我拖动时进行动画处理.我如何阻止这种情况发生?

I'm building a custom modal and when I drag the modal, any subviews that have animation's attached, they animate while I'm dragging. How do I stop this from happening?

我想过传递一个带有 isDragging 标志的 @EnvironmentObject,但它的可扩展性不是很好(并且不适用于自定义 ButtonStyle代码>s)

I thought about passing down an @EnvironmentObject with a isDragging flag, but it's not very scalable (and doesn't work well with custom ButtonStyles)

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
            .showModal(isShowing: .constant(true))
    }
}

extension View {
    func showModal(isShowing: Binding<Bool>) -> some View {
        ViewOverlay(isShowing: isShowing, presenting: { self })
    }
}

struct ViewOverlay<Presenting>: View where Presenting: View {
    @Binding var isShowing: Bool
    
    let presenting: () -> Presenting
    
    @State var bottomState: CGFloat = 0
    
    var body: some View {
        ZStack(alignment: .center) {
            presenting().blur(radius: isShowing ? 1 : 0)
            VStack {
                if isShowing {
                    Container()
                        .background(Color.red)
                        .offset(y: bottomState)
                        .gesture(
                            DragGesture()
                                .onChanged { value in
                                    bottomState = value.translation.height
                                }
                                .onEnded { _ in
                                    if bottomState > 50 {
                                        withAnimation {
                                            isShowing = false
                                        }
                                    }
                                    bottomState = 0
                                })
                        .transition(.move(edge: .bottom))
                }
            }
        }
    }
}

struct Container: View {
    var body: some View {
// I want this to not animate when dragging the modal
        Text("CONTAINER")
            .frame(maxWidth: .infinity, maxHeight: 200)
            .animation(.spring())
    }
}


更新:

extension View {
    func animationsDisabled(_ disabled: Bool) -> some View {
        transaction { (tx: inout Transaction) in
            tx.animation = tx.animation
            tx.disablesAnimations = disabled
        }
    }
}


Container()
   .animationsDisabled(isDragging || bottomState > 0)

在现实生活中,Container 包含一个按钮,按钮的按下状态有动画

In real life the Container contains a button with an animation on its pressed state

struct MyButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.9 : 1)
            .animation(.spring())
    }
}

在子视图中添加了 animationsDisabled 函数,这实际上会阻止子视图在拖动过程中移动.

Added the animationsDisabled function to the child view which does in fact stop the children moving during the drag.

它不会在最初滑入或消失时停止动画.

What it doesn't do is stop the animation when the being initially slide in or dismissed.

有没有办法知道视图何时基本上没有移动/过渡?

Is there a way to know when a view is essentially not moving / transitioning?

推荐答案

理论上 SwiftUI 不应该在这种情况下转换动画,但是我不确定这是否是一个错误 - 我不会以那种通用方式在 Container 中使用动画.我使用动画越多,就越倾向于将它们直接连接到特定值.

Theoretically SwiftUI should not translate animation in this case, however I'm not sure if this is a bug - I would not use animation in Container in that generic way. The more I use animations the more tend to join them directly to specific values.

无论如何...这里有一个可能的解决方法 - 通过在中间注入不同的宿主控制器来破坏动画可见性.

Anyway... here is possible workaround - break animation visibility by injecting different hosting controller in a middle.

使用 Xcode 12/iOS 14 测试

Tested with Xcode 12 / iOS 14

struct ViewOverlay<Presenting>: View where Presenting: View {
    @Binding var isShowing: Bool
    
    let presenting: () -> Presenting
    
    @State var bottomState: CGFloat = 0
    
    var body: some View {
        ZStack(alignment: .center) {
            presenting().blur(radius: isShowing ? 1 : 0)
            VStack {
                    Color.clear
                if isShowing {
                        HelperView {
                    Container()
                        .background(Color.red)
                        }
                        .offset(y: bottomState)
                        .gesture(
                             DragGesture()
                                  .onChanged { value in
                                        bottomState = value.translation.height
                                  }
                                  .onEnded { _ in
                                        if bottomState > 50 {
                                             withAnimation {
                                                  isShowing = false
                                             }
                                        }
                                        bottomState = 0
                                  })
                        .transition(.move(edge: .bottom))
                }
                    Color.clear
            }
        }
    }
}

struct HelperView<Content: View>: UIViewRepresentable {
    let content: () -> Content
    func makeUIView(context: Context) -> UIView {
        let controller = UIHostingController(rootView: content())
        return controller.view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
    }
}

这篇关于如何阻止 SwiftUI DragGesture 为子视图设置动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆