如何在 SwiftUI 中将拖动手势限制在一个方向上?

How do I restrict a dragging gesture to one direction only in SwiftUI?

过去 2 周我一直很尴尬地试图解决这个问题。

我想做的是:

  1. 将我的幻灯片视图捕捉到屏幕底部
  2. 禁止向上拖动,只允许向下拖动卡片关闭

我试过的:

我试过通过将卡片的高度设置为屏幕的高度来调整卡片的大小。你可以看到这一行被注释掉了。这样做之后,我弄乱了卡片的偏移量并将其设置为看起来卡片实际上小于其大小的一半,高度约为 300。问题是当我慢慢向上滑动时,我可以看到隐藏在屏幕外的空白 space 。这不是我想要的效果

接下来我尝试做的是将卡片的高度更改为所需的高度。然后调整偏移量,使卡片位于我想要的位置。但是,我觉得手动调整它在不同的屏幕上并不可靠。所以我正在尝试计算出正确的数学方法,以便在它弹出时始终将其放置在屏幕的最底部。

最后,我只想让用户只能向下拖动而不能向上拖动。

非常感谢您的帮助。我花了很多时间四处留言、阅读、学习新事物,但我无法解决我的具体问题。

这是我的幻灯片卡片

    import SwiftUI

struct SigninView<Content: View> : View {
    @GestureState private var dragState = DragState.inactive
    @State var position = CardPosition.top
    
    var content: () -> Content
    var body: some View {
        let drag = DragGesture()
            .updating($dragState) { drag, state, transaction in
                state = .dragging(translation: drag.translation)
            }
            .onEnded(onDragEnded)
        
        return Group {
           // Handle()
            self.content()
        }
        .frame(height: 333) //UIScreen.main.bounds.height)
        .background(Color.purple)
        .cornerRadius(10.0)
        .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
        .offset(y: self.position.rawValue + self.dragState.translation.height)
        .animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
        .gesture(drag)
    }
    
    private func onDragEnded(drag: DragGesture.Value) {
        let verticalDirection = drag.predictedEndLocation.y - drag.location.y
        let cardTopEdgeLocation = self.position.rawValue + drag.translation.height
        let positionAbove: CardPosition
        let positionBelow: CardPosition
        let closestPosition: CardPosition
         
        if cardTopEdgeLocation <= CardPosition.middle.rawValue {
            positionAbove = .top
            positionBelow = .middle
        } else {
            positionAbove = .middle
            positionBelow = .bottom
        }
       
        if (cardTopEdgeLocation - positionAbove.rawValue) < (positionBelow.rawValue - cardTopEdgeLocation) {
            closestPosition = positionAbove
        } else {
            closestPosition = positionBelow
        }
       
        if verticalDirection > 0 {
            self.position = positionBelow
        } else if verticalDirection < 0 {
            self.position = positionAbove
        } else {
            self.position = closestPosition
        }
         
    }
}

enum CardPosition: CGFloat {
    case top = 100
    case middle = 790
    case bottom = 850
}

enum DragState {
    case inactive
    case dragging(translation: CGSize)
    
    var translation: CGSize {
        switch self {
        case .inactive:
            return .zero
        case .dragging(let translation):
            return translation
        }
    }
    
    var isDragging: Bool {
        switch self {
        case .inactive:
            return false
        case .dragging:
            return true
        }
    }
}

这是我的 ContentView 页面,我在其中进行了测试:

import SwiftUI

struct ContentView: View {
    @State var show:Bool = false

     var body: some View {
        SigninView {
            VStack {
                
                Text("TESTING")
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Color.blue)
            }
        }
              
        
    }
  

}


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

首先,您可能不应该SigninView成为您的内容视图。相反,请考虑将您的登录视图显示为 overlay

var body: some View {
    ZStack {
        Text("Content here!")
    }
    .overlay(
        SigninView()
            .offset(...),
        alignment: .bottom
    )
}

这会自动将您的视图放置在屏幕底部 SigninView 的高度,这里应该几乎不涉及任何数学运算。偏移量,您将使用您的手势和您希望存在于底部和叠加层之间的任何 space 来定义。

接下来,只允许向下手势,你不能夹住你的翻译吗?

    var translation: CGSize {
        switch self {
        case .inactive:
            return .zero
        case .dragging(let translation):
            return max(0, translation) // clamp this to the actual translation or 0 so it can't go negative
        }
    }