I want the items inside LazyVGrid to have a tap modifier (change their scale) and at the same time be able to scroll. I set up a tap modifier for each element and it really works, but the ability to scroll the content disappears, but if I disable my custom tap effect, then scrolling becomes available again. How can I make a click effect and the ability to scroll the content at the same time?
struct ScaledTappable: ViewModifier {
@State var state = false
var tapHandler: () -> Void
func body(content: Content) -> some View {
content
.scaleEffect(state ? 0.9 : 1)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged({ value in
withAnimation(.smooth(duration: 0.2)) {
state = true
}
})
.onEnded({ value in
withAnimation(.bouncy(duration: 0.5)) {
state = false
tapHandler()
}
})
)
}
}
extension View {
@ViewBuilder
func tappable(enabled: Bool = true, onTap: @escaping () -> Void) -> some View {
if enabled {
self.modifier(ScaledTappable(tapHandler: onTap))
} else {
self.opacity(0.3)
}
}
}
2
Answers
This is is caused because your DragGesture is interfering with the scrolling behavior. You need to wrap your DragGesture code into SimultaneousGesture to make it work. So instead of using gesture modifier, use simultaneousGesture to combine the scrolling with tap gesture.
Instead of using a
DragGesture
to intercept taps, try using.onTapGesture
. Then, use a completion callback on the animation to perform the follow-on action (requires iOS 17):Another way to perform the animation would be to use
.phaseAnimator
(also requires iOS 17):For earlier iOS versions, consider using an
Animatable
ViewModifier
for performing the follow-on action after the first part of the animation has completed. See this answer for a generic implementation.Example of doing it this way:
Example use (same for all implementation variants):