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