简体   繁体   English

SwiftUI 中 DragGesture 和 ScrollView 的交互

[英]Interaction of DragGesture and ScrollView in SwiftUI

In an app I'm working on, there is a part that has, mostly, a "forward" navigation – tapping on buttons would display the next slide.在我正在开发的应用程序中,有一部分主要是“前进”导航——点击按钮将显示下一张幻灯片。 However, a secondary "backward" navigation is also necessary.然而,辅助“向后”导航也是必要的。 Here's the approach I've used:这是我使用的方法:

import SwiftUI

struct Sample: View {
    @State private var dragOffset: CGFloat = -100
    var body: some View {
        VStack {

            Text("Perhaps a title")

            ScrollView {
                VStack {
                    Text("Some scrollable content is going to be here")

                    // ...

                    Button(action: {
                        // Go to the next slide
                    }) { Text("Next") }
                }
            }

            Text("and, maybe, something else")
        }
        .overlay(
            Image(systemName: "arrow.left").offset(x: dragOffset / 2),
            alignment: .leading
        )
        .gesture(
            DragGesture()
                .onChanged{
                    self.dragOffset = $0.translation.width
                }
                .onEnded {
                    self.dragOffset = -100 // Hide the arrow

                    if $0.translation.width > 100 {
                        // Go to the previous slide
                    }
                }
        )
    }
}

There is a small indicator (left arrow) that is, initially, hidden (dragOffset = -100).有一个小指标(左箭头),最初是隐藏的(dragOffset = -100)。 When the drag gesture begins, offset is fed into the dragOffset state variable and that, effectively, shows the arrow.当拖动手势开始时,偏移量被输入到dragOffset状态变量中,并且有效地显示箭头。 When drag gesture ends, the arrow is hidden again and, if a certain offset is reached, the previous slide is displayed.当拖动手势结束时,箭头再次隐藏,如果达到某个偏移量,则显示上一张幻灯片。

Works well enough, except, when the user scrolls the content in the ScrollView, this gesture is also triggered and updated for a while but then is, I assume, cancelled by the ScrollView and the "onEnded" is not called.工作得很好,除了当用户滚动 ScrollView 中的内容时,这个手势也会被触发并更新一段时间,但我认为,它被 ScrollView 取消并且不调用“onEnded”。 As a result, the arrow indicator stays on the screen.结果,箭头指示符停留在屏幕上。

Hence the question: what is the correct way to do a gesture like that, that would work together with a ScrollView?因此,问题是:做这样的手势的正确方法是什么,可以与 ScrollView 一起使用? Is it even possible with the current state of SwiftUI? SwiftUI 的当前状态是否有可能?

For such temporary states it is better to use GestureState as it is automatically reset to initial state after gesture cancels/finished.对于这种临时状态,最好使用GestureState ,因为它会在手势取消/完成后自动重置为初始状态。

So here is possible approach所以这是可能的方法

Update: retested with Xcode 13.4 / iOS 15.5更新:使用 Xcode 13.4 / iOS 15.5 重新测试

Demo:演示:

在此处输入图像描述

Code:代码:

struct Sample: View {
    @GestureState private var dragOffset: CGFloat = -100
    var body: some View {
        VStack {

            Text("Perhaps a title")

            ScrollView {
                VStack {
                    Text("Some scrollable content is going to be here")

                    // ...

                    Button(action: {
                        // Go to the next slide
                    }) { Text("Next") }
                }
            }

            Text("and, maybe, something else")
        }
        .overlay(
            Image(systemName: "arrow.left").offset(x: dragOffset / 2),
            alignment: .leading
        )
        .gesture(
            DragGesture()
                .updating($dragOffset) { (value, gestureState, transaction) in
                    let delta = value.location.x - value.startLocation.x
                    if delta > 10 { // << some appropriate horizontal threshold here
                        gestureState = delta
                    }
                }
                .onEnded {
                    if $0.translation.width > 100 {
                        // Go to the previous slide
                    }
                }
        )
    }
}

Note: dragOffset: CGFloat = -100 this might have different effect on different real devices, so probably it is better to calculate it explicitly.注意: dragOffset: CGFloat = -100这可能对不同的真实设备有不同的影响,所以可能最好明确计算。

backup 备份

If you don't want to use @GestureState it's also possible to use DragGesture(minimumDistance: 0.0) .如果您不想使用@GestureState也可以使用DragGesture(minimumDistance: 0.0) This lets the DragGesture start before the scroll gesture is activated.这让 DragGesture 在滚动手势被激活之前启动。 Though this does seem like a bug on Apple's part to me.虽然这对我来说似乎是苹果的一个错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM