DragGesture阻止在SwiftUI中触摸滑块

4jb9z9bj  于 2023-06-04  发布在  Swift
关注(0)|答案(2)|浏览(171)

让我们看看这段代码:

@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似乎消耗触摸,尽管使用了simultaneousGesture。我也试过添加GestureMask.all,但也不起作用。我该怎么解决这个问题?我需要跟踪触摸下/上,因为这个滑块是用来寻求一个音频轨道,但也音频轨道更新滑块。但是,当用户触摸滑块时,它不应该这样做(sliderMoving标志应该处理这个问题)。
类似的方法适用于按钮敲击,但不适用于滑块移动。
编辑:
我发布了最终答案,感谢@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无论如何,它在我的情况下完美地工作。

icomxhvb

icomxhvb1#

看看这个解决方案是否适合你。正如我在评论中提到的,它基本上完全覆盖了滑块的手势阅读器。为了得到宽度,我使用了几何阅读器。你甚至可以使用.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
                    }
            )
            
        }
    }
}
xcitsw88

xcitsw882#

干得好,@Marco Boerner和@Makalele。我今天也遇到了同样的问题,在你的帮助下,我创造了一个更通用的解决方案。在我的特定用例中,我有一个带有自定义DragGesture的Button(用于捕获向下点击和向上点击事件)。当按下按钮时,它会阻止滑块视图更新,因为我的自定义DragGesture正在优先考虑。
关于代码的一些评论:

  • 我使用一个自定义修饰符“saveSize”来计算滑块的大小并将其存储到@State变量中。这很重要,因为我们需要知道滑块的宽度来计算新值。宽度将从屏幕到屏幕改变,或者当方向改变时……
  • 我选择使用一个.highPriorityGesture作为滑块的自定义手势扩展。这样做可使滑块的行为与正常情况一样。我们的自定义代码只会在Slider的DragGesture没有触发的情况下运行。在我的例子中,当另一个DragGesture当前在同一个视图容器中处于活动状态时。

首先,这里是计算滑块大小的修改器:

struct SizeCalculator: ViewModifier {
    
    @Binding var size: CGSize
    
    func body(content: Content) -> some View {
        content
            .background(
                GeometryReader { proxy in
                    Color.clear // we just want the reader to get triggered, so let's use an empty color
                        .onAppear {
                            size = proxy.size
                        }
                }
            )
    }
}

extension View {
    func saveSize(in size: Binding<CGSize>) -> some View {
        modifier(SizeCalculator(size: size))
    }
}

接下来,这里是带有自定义手势扩展的Slider,它调用一个函数来计算新的滑块值:

@State var sliderValue = 5000
@State var frequencySliderSize: CGSize = .zero

...

Slider(value: $sliderValue, in: 100...20000, step: 100)
    .saveSize(in: $sliderSize) // -> SAVE THE REAL-TIME SIZE OF THE SLIDER
    .highPriorityGesture(
        DragGesture(minimumDistance: geo.size.width * 0.01)
            .onChanged { gesture in
                sliderValue = calculateNewValue(
                    x: gesture.location.x,
                    width: sliderSize.width, // -> USE THE REAL-TIME WIDTH
                    sliderMin: Float(100),
                    sliderMax: Float(20000),
                    step: 100
                )
            }
        )

以及计算新值的函数:

private func calculateNewValue(x: CGFloat, width: CGFloat, sliderMin: Float, sliderMax: Float, step: Float) -> Double {
    
    // What percent filled are we at the X position?
    var percentFillAtX = x / width
    
    // Clamp between [0, 1]
    percentFillAtX = max(percentFillAtX, 0)
    percentFillAtX = min(percentFillAtX, 1)
    
    // Calculate the final value and clamp to the step
    let value = ((sliderMax - sliderMin) * Float(percentFillAtX)) + sliderMin
    let stepRemainder = value.truncatingRemainder(dividingBy: step)
    
    return Double(value - stepRemainder)
}

也许iOS 17会解决所有这些问题,这样我们就不必创建这些解决方案了。

相关问题