DragGesture 块在 SwiftUI 中触摸滑块

Posted

技术标签:

【中文标题】DragGesture 块在 SwiftUI 中触摸滑块【英文标题】:DragGesture blocks touching a Slider in SwiftUI 【发布时间】:2020-11-02 07:59:22 【问题描述】:

我们来看看这段代码:

@State private var progress: TimeInterval = 0
@State private var sliderMoving: Bool = false

Slider(value: $progress, in: 0 ... Double(100), onEditingChanged:  didChange in
    seekToProgress(p: progress)
).simultaneousGesture(
    DragGesture(minimumDistance: 0)
        .onChanged  gesture in
            print("gesture onChanged")
            sliderMoving = true
        
        .onEnded  gesture in
            print("gesture onEnded")
            sliderMoving = false
        
)

我想跟踪滑块上的触地和结束事件,但这会阻止实际的滑块被触摸。换句话说,尽管使用了同步手势,但 DragGesture 似乎消耗了触摸。我也尝试过添加 GestureMask.all,但也没有用。我怎样才能解决这个问题?我需要跟踪触摸向下/向上,因为此滑块用于查找音轨,而且音轨也会更新滑块。然而它不应该这样做,当用户触摸滑块时(滑块移动标志应该处理这个)。

类似的方法适用于按钮点击,但不适用于滑块移动。

编辑:

我正在发布最终答案,感谢@Marco Boerner 的大力帮助:

GeometryReader  geometry in
    Slider(value: $progress, in: 0 ... Double(100), onEditingChanged:  didChange in
        
        print("onEditingChanged") //not used
        
    )
    .simultaneousGesture(
        DragGesture(minimumDistance: 0)
            .onChanged  gesture in
                sliderMoving = true
                updateProgress(x: gesture.location.x, width: geometry.size.width)
            
            .onEnded  gesture in
                sliderMoving = false
                updateProgress(x: gesture.location.x, width: geometry.size.width)
                seekToProgress(p: progress)
            
        )


private func updateProgress(x: CGFloat, width: CGFloat) 
    let knobWidth: CGFloat = 30
    if x < knobWidth / 2 
        progress = TimeInterval(0)
     else if x >= width - knobWidth / 2 
        progress = TimeInterval(100)
     else 
        let x2 = x - knobWidth / 2
        let width2 = width - knobWidth
        progress = TimeInterval(x2 / (width2 / 100))
     

唯一的缺点是我们假设旋钮有一个固定值,但因为我将滑块高度设置为 30,所以在我的情况下它完美无缺。

【问题讨论】:

我们遇到了类似的问题并尝试了各种方法。我认为你不能同时运行两个相同的手势。假设 Slider 有自己的 DragGesture 他们似乎在干扰。使用 TapGesture 而不是 DragGesture 可用于上触,但不会记录触地。您是否尝试过将sliderMoving = true 移动到滑块本身的didChange 内,并将sliderMoving = false 与TapGesture 的.onEnded 一起使用? 这种方法不起作用 - onTapGesture 仅在点击到位时才被调用,更糟糕的是 didChange 总是在它之后调用。所以它只是在播放曲目时阻止滑块更新。 好的,我想我想错了。播放音轨时如何以编程方式更新 Slider?我的想法是设置您的 DragGesture 以编程方式更新滑块。基本上完全绕过了滑块手势识别器。 好的,我试过了,它似乎对我的情况和理解有效。如果这是您正在寻找的解决方案,请尝试一下。 :) 【参考方案1】:

看看这个解决方案是否适合你。正如我在评论中提到的,它基本上完全覆盖了滑块的手势阅读器。为了获得宽度,我使用了几何阅读器。您甚至可以使用.gesture 甚至.highPriorityGesture 而不是simultaneousGesture 此外,根据您放置GeometryReader 的位置,您可能必须使用手势的.local coordinateSpace

struct ContentView: View 

@State private var progress: TimeInterval = 0
@State private var sliderMoving: Bool = false


var body: some View 
    
    GeometryReader  geometry in
        
        Slider(value: $progress, in: 0 ... Double(100), onEditingChanged:  didChange in
            
        ).simultaneousGesture(
            DragGesture(minimumDistance: 0)
                .onChanged  gesture in
                    print("gesture onChanged")
                    sliderMoving = true
                    progress = TimeInterval(gesture.location.x / (geometry.size.width / 100))
                
                .onEnded  gesture in
                    print("gesture onEnded")
                    sliderMoving = false
                    progress = TimeInterval(gesture.location.x / (geometry.size.width / 100))
                    
            )
            
        
    

更新并添加以下评论。这可以选择调整填充修改器和旋钮大小。根据滑块的设置方式,可能需要进行不同的调整才能获得准确的位置。我目前不知道有一种方法可以获取滑块各个部分的确切位置。自定义滑块可能会解决此问题。

struct ContentView6: View 
    
    @State private var progress: TimeInterval = 0
    @State private var sliderMoving: Bool = false

    var body: some View 
        
        GeometryReader  geometry in
            
            let padding: CGFloat = 0 //optional in case padding needs to be adjusted.
            let adjustment: CGFloat = padding + 15
            
            Slider(value: $progress, in: 0 ... Double(100), onEditingChanged:  didChange in
                
            )
            .padding(padding)
            .simultaneousGesture(
                DragGesture(minimumDistance: 0)
                    .onChanged  gesture in
                        sliderMoving = true
                        progress = TimeInterval( min(max((gesture.location.x - adjustment) / ((geometry.size.width - adjustment*2) / 100), 0), 100)  )
                        print(progress)
                    
                    .onEnded  gesture in
                        sliderMoving = false
                    
            )
            
        
    

【讨论】:

很好,这行得通!添加 GeometryReader 会导致高度问题(滑块的高度很大),但我通过硬编码帧高度来修复它。这个解决方案效果很好,只有一个小问题:通过将gesture.location.x 除以geometry.size.width 计算的进度不完全是旋钮所指向的,因为它的左右边距很小(在滑块上)。 你想好如何解决边距问题了吗?我看到的一个问题是实际的“播放头”不等于旋钮中心。我认为由于旋钮较大,在滑块的左侧,旋钮左边缘被认为是 0,在滑块的中心是旋钮中心,在右侧是旋钮右侧。但是,如果您在滑块上使用其他修饰符(如 .padding),它也会产生影响,因为这会改变其几何形状。在这种情况下,您必须调整计算。如果这有助于解决问题,请查看我的更新答案。 问题是我没有向滑块添加任何填充。我可能没有很清楚地解释这个问题:旋钮有它的大小,所以改变进度值的“实际”区域应该是:knobWidth / 2 ... geometry.width - 旋钮宽度 / 2,所有低于旋钮宽度 / 2 应该被认为是 0,geometry.width -knobWidth / 2 之后的所有内容都应该被认为是 100。这是非常小的细节,但它就在那里,你可以在玩滑块时体验它。 好吧,我想通了。我发布了最终解决方案:) 我想我也简要提到了这一点。我认为问题在于,旋钮没有超过滑块,为了让旋钮指向 0 或 100,旋钮的中心正在移动。从左到右移动旋钮也会移动它的中心。我同意,我能想到的唯一改变方法是调整旋钮宽度的一半。在我的示例中,将填充设置为 0 并调整为滑块侧的一半。我认为 Apple 正在为滑块 (SliderStyle) 计划类似于 ButtonStyle 的东西,希望这可以让您更改样式并因此更改滑块的中心。

以上是关于DragGesture 块在 SwiftUI 中触摸滑块的主要内容,如果未能解决你的问题,请参考以下文章

在触发不同的 DragGesture 之前,SwiftUI DragGestures 不会更新

SwiftUI 中 DragGesture 和 ScrollView 的交互

DragGesture 正在取消 SwiftUI 中的 LongPressGesture

如何在 SwiftUI 中实现触发 switch case 的左或右 DragGesture()?

SwiftUI 中的选择器选择和翻译冲突

如何检测 SwiftUI 视图何时被拖过