Взаимодействие DragGesture и ScrollView в SwiftUI
В приложении, над которым я работаю, есть часть, в которой в основном предусмотрена навигация "вперед" - нажатие на кнопки приведет к отображению следующего слайда. Однако необходима и вторичная "обратная" навигация. Вот подход, который я использовал:
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
}
}
)
}
}
Есть небольшой индикатор (стрелка влево), который изначально скрыт (dragOffset = -100). Когда начинается жест перетаскивания, смещение вводится в переменную состояния dragOffset, и это, по сути, показывает стрелку. По окончании жеста перетаскивания стрелка снова скрывается, и при достижении определенного смещения отображается предыдущий слайд.
Работает достаточно хорошо, за исключением того, что когда пользователь прокручивает контент в ScrollView, этот жест также запускается и обновляется на некоторое время, но затем, как я полагаю, отменяется ScrollView, и onEnded не вызывается. В результате стрелка индикатора остается на экране.
Отсюда вопрос: как правильно сделать такой жест, который будет работать вместе с ScrollView? Возможно ли такое при текущем состоянии SwiftUI?
2 ответа
Для таких временных состояний лучше использовать GestureState
так как он автоматически возвращается в исходное состояние после отмены / завершения жеста.
Итак, здесь возможен подход
Демо:
Код:
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
}
}
)
}
}
Примечание: dragOffset: CGFloat = -100
это может иметь разный эффект на разных реальных устройствах, поэтому, вероятно, лучше рассчитать его явно.
Если вы не хотите использовать@GestureState
также можно использоватьDragGesture(minimumDistance: 0.0)
. Это позволяет запустить DragGesture до активации жеста прокрутки. Хотя мне это кажется ошибкой со стороны Apple.