While DragGesture
works well in many scenarios, it can have some unwanted side effects such as when used within a scroll view, it will take precedence over the scroll view's built-in gesture handing.
In SwiftUI, Button
s already keep track of the pressed state, so a solution to this problem is to use a custom ButtonStyle
to allow for hooking into changes in the isPressed
state.
Here's a working solution:
Define a custom ButtonStyle
:
struct CustomButtonStyle: ButtonStyle {
var onPressed: () -> Void
var onReleased: () -> Void
// Wrapper for isPressed where we can run custom logic via didSet (or willSet)
@State private var isPressedWrapper: Bool = false {
didSet {
// new value is pressed, old value is not pressed -> switching to pressed state
if (isPressedWrapper && !oldValue) {
onPressed()
}
// new value is not pressed, old value is pressed -> switching to unpressed state
else if (oldValue && !isPressedWrapper) {
onReleased()
}
}
}
// return the label unaltered, but add a hook to watch changes in configuration.isPressed
func makeBody(configuration: Self.Configuration) -> some View {
return configuration.label
.onChange(of: configuration.isPressed, perform: { newValue in isPressedWrapper = newValue })
}
}
You could also write the didSet
logic directly in the perform
block of the onChange
modifier, but I think this keeps it looking clean.
Wrap your clickable view with Button
struct ExampleView: View {
@State private var text: String = "Unpressed"
var body: some View {
Text(self.text)
Button(action: { ... }, label: {
// your content here
}).buttonStyle(CustomButtonStyle(onPressed: {
self.text = "Pressed!"
}, onReleased: {
self.text = "Unpressed"
}))
}
}
Then you can pass whatever logic you want to the onPressed
and onReleased
parameters to CustomButtonStyle
.
I've used this to allow for custom onPressed
handling of clickable rows in a ScrollView
.