如何在 SwifUI 中构建具有“粘性”行为的 DragGesture?

问题描述 投票:0回答:1

我正在 Rectangle() 中构建一个简单的拖动手势动画,在手势结束时,将形状粘贴到组中最近的项目中。

检查下图中的示例。

我通过以下方法取得了不错的结果:

  • 手动计算黑色矩形的x绝对位置:

    @State private var xPos : [Int] = [40,110,180,250,320]

  • 使用绝对位置属性将矩形放置在屏幕中:

    Rectangle()
        .position(x: CGFloat(xPos[i]), y: 0)
    
  • 最后,在蓝色矩形中,添加手势属性,在 .onEnded 上使用一个奇特的函数来查找最接近的元素,并相应地应用 x 位置。

     Rectangle()
              .fill(.blue)
              .position(location)
              .gesture(
                  DragGesture()
                      .onChanged { gesture in
                          location.x = gesture.location.x
                      }
                      .onEnded { value in
                          withAnimation(.spring()) {
                              var goal = Int(value.location.x)
                              var closest = xPos[0]
                              var index = 0
    
                              for item in xPos {
                                  var distanceToActual = abs(closest - goal)
                                  if (xPos.count - 1 > index) { // prevent out of the bound
                                      var distanceToNext = abs(xPos[index + 1] - goal)
                                      if(distanceToNext < distanceToActual) {
                                          closest = xPos[index + 1]
                                      }
                                  }
                                  index += 1
                              }
    
                              location.x = CGFloat(closest)
                          }
                      }
    

我的问题是,有没有更优雅的方法?手动计算黑色矩形的位置是非常烦人的...有什么方法可以将黑色矩形放置在 HStack 中而不是手动定位吗?

swift swiftui gesture hstack draggesture
1个回答
0
投票

答案中描述的技术是否可以检测当前哪个视图落在 DragGesture 的位置下?可用于检测哪个方块最接近拖动位置(这是我的答案)。

matchedGeometryEffect

 然后提供了一种方便的方法将蓝色矩形的位置与识别的正方形相匹配。

像这样:

@State private var dragLocation = CGPoint.zero @State private var indexForDragLocation = 0 @Namespace private var ns let spacing: CGFloat = 50 var body: some View { HStack(spacing: spacing) { ForEach(0...4, id: \.self) { index in Color(white: 0.2) .frame(width: 20, height: 20) .matchedGeometryEffect(id: index, in: ns, isSource: index == indexForDragLocation) .background { GeometryReader { proxy in let width = proxy.size.width let midX = proxy.frame(in: .global).midX let dx = abs(midX - dragLocation.x) let isClosest = dx < (width + spacing) / 2 Color.clear .onChange(of: isClosest) { oldVal, newVal in if isClosest { indexForDragLocation = index } } } } } } .background { RoundedRectangle(cornerRadius: 4) .fill(.blue) .frame(width: 40, height: 80) .matchedGeometryEffect( id: indexForDragLocation, in: ns, properties: .position, isSource: false ) .animation(.spring, value: indexForDragLocation) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(UIColor.systemBackground)) .gesture( DragGesture(coordinateSpace: .global) .onChanged { val in dragLocation = val.location } ) }

Animation

© www.soinside.com 2019 - 2024. All rights reserved.