SwiftUI中DragGesture和ScrollView的交互
Interaction of DragGesture and ScrollView in SwiftUI
在我正在开发的应用程序中,有一个部分主要有一个 "forward" 导航 – 点击按钮将显示下一张幻灯片。但是,辅助 "backward" 导航也是必需的。这是我使用的方法:
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 = [=10=].translation.width
}
.onEnded {
self.dragOffset = -100 // Hide the arrow
if [=10=].translation.width > 100 {
// Go to the previous slide
}
}
)
}
}
有一个小指示器(左箭头),最初是隐藏的 (dragOffset = -100)。当拖动手势开始时,offset 被输入 dragOffset 状态变量,并且有效地显示箭头。当拖动手势结束时,箭头再次隐藏,如果达到某个偏移量,则显示上一张幻灯片。
工作得很好,除了当用户滚动 ScrollView 中的内容时,这个手势也会被触发并更新一段时间,但我假设它被 ScrollView 取消了,"onEnded" 是没有叫。结果,箭头指示器停留在屏幕上。
因此问题来了:做这样一个可以与 ScrollView 一起工作的手势的正确方法是什么? SwiftUI 的当前状态甚至可能吗?
对于这种临时状态,最好使用 GestureState
,因为它会在手势后自动重置为初始状态 cancels/finished。
所以这是可能的方法
更新:用 Xcode 13.4 / iOS 15.5
重新测试
演示:
代码:
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 [=10=].translation.width > 100 {
// Go to the previous slide
}
}
)
}
}
注意:dragOffset: CGFloat = -100
这可能对不同的真实设备有不同的影响,所以最好明确地计算它。
在我正在开发的应用程序中,有一个部分主要有一个 "forward" 导航 – 点击按钮将显示下一张幻灯片。但是,辅助 "backward" 导航也是必需的。这是我使用的方法:
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 = [=10=].translation.width
}
.onEnded {
self.dragOffset = -100 // Hide the arrow
if [=10=].translation.width > 100 {
// Go to the previous slide
}
}
)
}
}
有一个小指示器(左箭头),最初是隐藏的 (dragOffset = -100)。当拖动手势开始时,offset 被输入 dragOffset 状态变量,并且有效地显示箭头。当拖动手势结束时,箭头再次隐藏,如果达到某个偏移量,则显示上一张幻灯片。
工作得很好,除了当用户滚动 ScrollView 中的内容时,这个手势也会被触发并更新一段时间,但我假设它被 ScrollView 取消了,"onEnded" 是没有叫。结果,箭头指示器停留在屏幕上。
因此问题来了:做这样一个可以与 ScrollView 一起工作的手势的正确方法是什么? SwiftUI 的当前状态甚至可能吗?
对于这种临时状态,最好使用 GestureState
,因为它会在手势后自动重置为初始状态 cancels/finished。
所以这是可能的方法
更新:用 Xcode 13.4 / iOS 15.5
重新测试演示:
代码:
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 [=10=].translation.width > 100 {
// Go to the previous slide
}
}
)
}
}
注意:dragOffset: CGFloat = -100
这可能对不同的真实设备有不同的影响,所以最好明确地计算它。