如何阻止SwiftUI Dragesture设置子视图动画

如何阻止SwiftUI Dragesture设置子视图动画,swiftui,draggesture,Swiftui,Draggesture,我正在构建一个自定义模态,当我拖动该模态时,任何附加了动画的子视图都会在我拖动的同时设置动画。我该如何阻止这种情况发生 我曾考虑过传递一个带有isDraging标志的@EnvironmentObject,但它的可伸缩性不是很好(并且不能与自定义按钮样式一起使用) 在现实生活中,容器包含一个按钮,其按下状态带有动画 struct MyButtonStyle: ButtonStyle { func makeBody(configuration: Self.Configuration) -&g

我正在构建一个自定义模态,当我拖动该模态时,任何附加了动画的子视图都会在我拖动的同时设置动画。我该如何阻止这种情况发生

我曾考虑过传递一个带有
isDraging
标志的
@EnvironmentObject
,但它的可伸缩性不是很好(并且不能与自定义
按钮样式一起使用)

在现实生活中,容器包含一个按钮,其按下状态带有动画

struct MyButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.9 : 1)
            .animation(.spring())
    }
}
将animationsDisabled功能添加到子视图中,该功能实际上会在拖动过程中停止子视图的移动

它没有做的是在动画最初滑入或消失时停止动画


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

所以这是我最新的答案。我不认为有一个很好的方法可以做到这一点,所以现在我用一个自定义按钮

import SwiftUI

struct ContentView: View {
    @State var isShowing = false
    var body: some View {
        Text("Hello, world!")
            .padding()
            .onTapGesture(count: 1, perform: {
                withAnimation(.spring()) {
                    self.isShowing.toggle()
                }
            })
            .showModal(isShowing: self.$isShowing)
    }
}

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
    @State var isDragging = false
    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
                                    isDragging = true
                                    bottomState = value.translation.height
                                    
                                }
                                .onEnded { _ in
                                    isDragging = false
                                    if bottomState > 50 {
                                        withAnimation(.spring()) {
                                            isShowing = false
                                        }
                                    }
                                    bottomState = 0
                                })
                        .transition(.move(edge: .bottom))
                }
            }
        }
    }
}

struct Container: View {
    var body: some View {
        CustomButton(action: {}, label: {
            Text("Pressme")
        })
        .frame(maxWidth: .infinity, maxHeight: 200)
    }
}

struct CustomButton<Label >: View where Label: View {
    @State var isPressed = false
    var action: () -> ()
    var label: () -> Label
    var body: some View {
        label()
            .scaleEffect(self.isPressed ? 0.9 : 1.0)
        .gesture(DragGesture(minimumDistance: 0).onChanged({_ in
            withAnimation(.spring()) {
                self.isPressed = true
            }
        }).onEnded({_ in
            withAnimation(.spring()) {
                self.isPressed = false
                action()
            }
        }))
    }
}
导入快捷界面
结构ContentView:View{
@状态变量isShowing=false
var body:一些观点{
文本(“你好,世界!”)
.padding()
.ONTAPPORATE(计数:1,执行:{
使用动画(.spring()){
self.isShowing.toggle()
}
})
.showModal(isShowing:self.$isShowing)
}
}
扩展视图{
func showmodel(isShowing:Binding)->一些视图{
视图覆盖(显示:显示,显示:{self})
}
}
结构视图覆盖:视图显示位置:视图{
@绑定变量显示:Bool
让我们展示:()->展示
@状态变量bottomState:CGFloat=0
@状态变量IsDraging=false
var body:一些观点{
ZStack(对齐:。中心){
呈现().blur(半径:isShowing?1:0)
VStack{
如果是显示{
容器()
.背景(颜色.红色)
.偏移量(y:底部状态)
.手势(
德拉格斯特尔()
.onChanged{中的值
IsDraging=true
底部状态=value.translation.height
}
.onEnded{uuin
IsDraging=false
如果bottomState>50{
使用动画(.spring()){
isShowing=false
}
}
底部状态=0
})
.transition(.move(边:。底部))
}
}
}
}
}
结构容器:视图{
var body:一些观点{
自定义按钮(操作:{},标签:{
文本(“按我”)
})
.frame(最大宽度:无穷大,最大高度:200)
}
}
结构自定义按钮:视图,其中标签:视图{
@状态变量isPressed=false
变量作用:()->()
变量标签:()->标签
var body:一些观点{
标签()
.scaleEffect(self.isPressed?0.9:1.0)
.手势(DragGesture(最小距离:0)。一旦更改({uu}in
使用动画(.spring()){
self.isPressed=true
}
}).onEnded({uu}in
使用动画(.spring()){
self.isPressed=false
行动()
}
}))
}
}
问题是不能在容器内使用隐式动画,因为它们在容器移动时将被设置动画。因此,还需要使用
withAnimation
为按下的按钮显式设置动画,我现在使用自定义按钮和DragGesture进行了设置

这是显式动画和隐式动画之间的区别

请看此视频,其中详细探讨了此主题:


理论上,在这种情况下,SwiftUI不应该翻译动画,但我不确定这是否是一个bug-我不会以这种通用方式在容器中使用动画。我使用动画越多,就越倾向于将它们直接连接到特定值

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

使用Xcode 12/iOS 14进行测试

struct ViewOverlay:视图显示位置:视图{
@绑定变量显示:Bool
让我们展示:()->展示
@状态变量bottomState:CGFloat=0
var body:一些观点{
ZStack(对齐:。中心){
呈现().blur(半径:isShowing?1:0)
VStack{
颜色。清晰
如果是显示{
助手视图{
容器()
.背景(颜色.红色)
}
.偏移量(y:底部状态)
.手势(
德拉格斯特尔()
.onChanged{中的值
底部状态=value.translation.height
}
.onEnded{uuin
如果bottomState>50{
动画片{
isShowing=false
}
}
底部状态=0
})
.transition(.move(边:。底部))
}
颜色。清晰
}
}
}
}
结构HelperView:UIViewRepresentable{
让内容:()->内容
func makeUIView(上下文:context)->UIView{
让
struct MyButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.9 : 1)
            .animation(.spring())
    }
}
import SwiftUI

struct ContentView: View {
    @State var isShowing = false
    var body: some View {
        Text("Hello, world!")
            .padding()
            .onTapGesture(count: 1, perform: {
                withAnimation(.spring()) {
                    self.isShowing.toggle()
                }
            })
            .showModal(isShowing: self.$isShowing)
    }
}

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
    @State var isDragging = false
    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
                                    isDragging = true
                                    bottomState = value.translation.height
                                    
                                }
                                .onEnded { _ in
                                    isDragging = false
                                    if bottomState > 50 {
                                        withAnimation(.spring()) {
                                            isShowing = false
                                        }
                                    }
                                    bottomState = 0
                                })
                        .transition(.move(edge: .bottom))
                }
            }
        }
    }
}

struct Container: View {
    var body: some View {
        CustomButton(action: {}, label: {
            Text("Pressme")
        })
        .frame(maxWidth: .infinity, maxHeight: 200)
    }
}

struct CustomButton<Label >: View where Label: View {
    @State var isPressed = false
    var action: () -> ()
    var label: () -> Label
    var body: some View {
        label()
            .scaleEffect(self.isPressed ? 0.9 : 1.0)
        .gesture(DragGesture(minimumDistance: 0).onChanged({_ in
            withAnimation(.spring()) {
                self.isPressed = true
            }
        }).onEnded({_ in
            withAnimation(.spring()) {
                self.isPressed = false
                action()
            }
        }))
    }
}
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) {
    }
}
struct Container: View {
    
    @Binding var bottomState: CGFloat

              .
              .
              .
              .
}
Container(bottomState: $bottomState)
Text("CONTAINER")
            .frame(maxWidth: .infinity, maxHeight: 200)
            .animation(nil, value: bottomState) // You Need To Add This
            .animation(.spring())
.animation(nil, value: bottomState)
.animation(.spring())
.animation(.spring())
.animation(nil, value: bottomState)