I have an AsyncImage
that is zoomable via MagnifyGesture
and drawable via a view modifier. Dragging and zooming works as expected. But now I want to show an overlay via onLongPressGesture
, but this doesn't trigger. If I remove .draggable
it works. How can I fix it?
public struct ContentView: View {
@ObservedObject private var viewState: ViewState
@State private var showAltText: Bool = false
@State private var currentZoom = 0.0
@State private var totalZoom = 1.0
public init(viewState vs:ViewState) {
viewState = vs
}
public var body: some View {
ZStack {
VStack {
if viewState.selectedComicStrip != nil {
AsyncImage(url: viewState.selectedComicStrip!.imageURL) { image in
image.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 400, maxHeight: 600)
} placeholder: {
Text("placeholder").frame(maxWidth: 400, maxHeight: 600)
}
.draggable()
.scaleEffect(currentZoom + totalZoom)
.gesture(
MagnifyGesture()
.onChanged { value in
currentZoom = value.magnification - 1
}
.onEnded { value in
totalZoom += currentZoom
currentZoom = 0
}
)
.onLongPressGesture {
showAltText.toggle()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
showAltText = false
}
}
} else {
Text("placeholder")
}
}
}.fullScreenCover(isPresented: $showAltText, content: {
ZStack {
Color.black.opacity(0.8)
.edgesIgnoringSafeArea(.all)
Text(viewState.selectedComicStrip!.altText)
.foregroundStyle(.white)
.padding()
}
})
.padding()
}
}
//https://stackoverflow.com/a/63082240/106435
struct DraggableView: ViewModifier {
@State var offset = CGPoint(x: 0, y: 0)
func body(content: Content) -> some View {
content
.gesture(DragGesture(minimumDistance: 0)
.onChanged { value in
self.offset.x += value.location.x - value.startLocation.x
self.offset.y += value.location.y - value.startLocation.y
})
.offset(x: offset.x, y: offset.y)
}
}
extension View {
func draggable() -> some View {
return modifier(DraggableView())
}
}
According to this Hacking with Swift post and a few others, SwiftUI will only trigger one gesture at a time by default. However, as far as I can tell from the docs, the exact behavior is undefined in general:
When you add multiple gestures to your app’s view hierarchy, you need to decide how the gestures interact with each other. You use gesture composition to define the order SwiftUI recognizes gestures. [SwiftUI docs, emphasis mine.]
Inspired by this SO question, you could try a couple things:
simultaneousGesture
modifier. (Probably the most robust approach, suggested by the docs.)minimumDistance
on the DragGesture
. (That way, it doesn't trigger immediately and preclude the long-press gesture from triggering too.)onLongPressGesture
before the DragGesture
. (Probably the most fragile option.)