如何检测 SwiftUI View 何时被拖动

Vik*_*Seč 7 animation gesture ios swiftui

我想当用户在我的视图上拖动手指时触发动画。

\n

我可以做类似的事情

\n
Image(\xe2\x80\xa6)\n  .rotationEffect(\xe2\x80\xa6)\n  .animation(self.isAnimating ? .spring : .default)\n  .gesture(\n    DragGesture(minimumDistance: 5, coordinateSpace: .global)\n      .onChanged { value in\n        self.isAnimating = true\n        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {\n          self.isAnimating = false\n        }\n      }\n  )\n
Run Code Online (Sandbox Code Playgroud)\n

然而,这仅捕获从图像开始的拖动事件。从其他地方开始然后在图像上移动的拖动事件将被忽略。

\n

我还可以检测父视图中的拖动事件并计算哪个子视图被拖动到 \xe2\x80\x93 上,这很好。但是,我该如何告诉子视图进行动画处理呢?更新它们的属性会导致重新渲染,这当然会取消动画。

\n

像这样通过模型传递 ui 数据似乎是一种反模式。

\n

有什么建议么?

\n

Adr*_*ien 18

我用小 CardView 网格替换了示例的图像。我们将尝试更改通过拖动手势“交叉”的卡片的颜色。

我们可以用来PreferenceKey获取所有CardViews 边界...

struct CardPreferenceData: Equatable {
    let index: Int
    let bounds: CGRect
}

struct CardPreferenceKey: PreferenceKey {
    typealias Value = [CardPreferenceData]
    
    static var defaultValue: [CardPreferenceData] = []
    
    static func reduce(value: inout [CardPreferenceData], nextValue: () -> [CardPreferenceData]) {
        value.append(contentsOf: nextValue())
    }
}

Run Code Online (Sandbox Code Playgroud)

这里 :

struct CardView: View {
    let index: Int
    
    var body: some View {
        Text(index.description)
            .padding(10)
            .frame(width: 60)
            .overlay(RoundedRectangle(cornerRadius: 10).stroke())
            .background(
                GeometryReader { geometry in
                    Rectangle()
                        .fill(Color.clear)
                        .preference(key: CardPreferenceKey.self,
                                    value: [CardPreferenceData(index: self.index, bounds: geometry.frame(in: .named("GameSpace")))])
                }
            )
    }
}

Run Code Online (Sandbox Code Playgroud)

现在,在 ContentView 中,我们可以收集这些卡的所有首选项(边界和索引)并将它们存储在数组中:

.onPreferenceChange(CardPreferenceKey.self){ value in
            cardsData = value
        }
Run Code Online (Sandbox Code Playgroud)

我们现在可以将这些 CardView 的位置 ( bounds) 与拖动手势的位置进行比较。


struct ContentView: View {
    let columns = Array(repeating: GridItem(.fixed(60), spacing: 40), count: 3)
    @State private var selectedCardsIndices: [Int] = []
    @State private var cardsData: [CardPreferenceData] = []
    var body: some View {
        LazyVGrid(columns: columns, content: {
            ForEach((1...12), id: \.self) { index in
                CardView(index: index)
                    .foregroundColor(selectedCardsIndices.contains(index) ? .red : .blue)
            }
        })
        .onPreferenceChange(CardPreferenceKey.self){ value in
            cardsData = value
        }
        .gesture(
            DragGesture()
                .onChanged {drag in
                    if let data = cardsData.first(where: {$0.bounds.contains(drag.location)}) {
                        selectedCardsIndices.append(data.index)
                    }
                }
        )
        .coordinateSpace(name: "GameSpace")
    }
}

Run Code Online (Sandbox Code Playgroud)

例子

编辑:画布上不会出现视频开头的小“滞后”。仅在模拟器上。我还没有在真实设备上进行过测试。

  • 天啊,为什么这些看似简单的事情却如此复杂,我每年在 WWDC 之前都会祈祷苹果能对此做点什么,在那之前,为你+1。感谢分享! (2认同)