In an app I'm working on, there is a part that has, mostly, a "forward" navigation – tapping on buttons would display the next slide. However, a secondary "backward" navigation is also necessary. Here's the approach I've used:
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
}
}
)
}
}
There is a small indicator (left arrow) that is, initially, hidden (dragOffset = -100). When the drag gesture begins, offset is fed into the dragOffset state variable and that, effectively, shows the arrow. When drag gesture ends, the arrow is hidden again and, if a certain offset is reached, the previous slide is displayed.
Works well enough, except, when the user scrolls the content in the ScrollView, this gesture is also triggered and updated for a while but then is, I assume, cancelled by the ScrollView and the "onEnded" is not called. As a result, the arrow indicator stays on the screen.
Hence the question: what is the correct way to do a gesture like that, that would work together with a ScrollView? Is it even possible with the current state of SwiftUI?
For such temporary states it is better to use GestureState
as it is automatically reset to initial state after gesture cancels/finished.
So here is possible approach
Demo:
Code:
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
}
}
)
}
}
Note: dragOffset: CGFloat = -100
this might have different effect on different real devices, so probably it is better to calculate it explicitly.
backup
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With