I have a yellow button and a blue circle inside a gray container. I added
Here's my code:
struct ContentView: View {
@GestureState var containerOffset = CGSize.zero
@GestureState var circleOffset = CGSize.zero
var body: some View {
VStack { /// gray container
/// should move the gray container when dragged
Button {
print("Button Pressed")
} label: {
Text("Button")
.padding(50)
.background(.yellow)
}
/// should **NOT** move the gray container when dragged
/// How do I make it move the circle instead?
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.offset(circleOffset)
.gesture(
DragGesture() /// this never gets called!
.updating($circleOffset) { value, circleOffset, transaction in
circleOffset = value.translation
}
)
.padding(50)
}
.background(Color.gray)
.offset(containerOffset)
.simultaneousGesture( /// `simultaneous` is needed to allow dragging from the yellow button
DragGesture() /// move the gray container
.updating($containerOffset) { value, containerOffset, transaction in
containerOffset = value.translation
}
)
}
}
How do I stop the outer DragGesture
from swallowing the inner circle's DragGesture
?
CodePudding user response:
One way to do it is to add a state variable in ContentView
that tracks whether the circle's gesture is active. When the circle's gesture is active, use a GestureMask
of .subviews
on the outer gesture to prevent it from activating. You can do it with an @State
:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
@GestureState var containerOffset = CGSize.zero
@GestureState var circleOffset = CGSize.zero
@State var subviewDragIsActive = false
var body: some View {
VStack { /// gray container
/// should move the gray container when dragged
Button {
print("Button Pressed")
} label: {
Text("Button")
.padding(50)
.background(Color.yellow)
}
/// should **NOT** move the gray container when dragged
/// How do I make it move the circle instead?
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.offset(circleOffset)
.gesture(
DragGesture() /// this never gets called!
.updating($circleOffset) { value, circleOffset, transaction in
circleOffset = value.translation
}
.onChanged { _ in
subviewDragIsActive = true
}
.onEnded { _ in
subviewDragIsActive = false
}
)
.padding(50)
}
.background(Color.gray)
.offset(containerOffset)
.simultaneousGesture( /// `simultaneous` is needed to allow dragging from the yellow button
DragGesture() /// move the gray container
.updating($containerOffset) { value, containerOffset, transaction in
containerOffset = value.translation
},
including: subviewDragIsActive ? .subviews : .all
)
}
}
PlaygroundPage.current.setLiveView(ContentView())
or you can do it with an @GestureState
:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
@GestureState var containerOffset = CGSize.zero
@GestureState var circleOffset = CGSize.zero
@GestureState var subviewDragIsActive = false
var body: some View {
VStack { /// gray container
/// should move the gray container when dragged
Button {
print("Button Pressed")
} label: {
Text("Button")
.padding(50)
.background(Color.yellow)
}
/// should **NOT** move the gray container when dragged
/// How do I make it move the circle instead?
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.offset(circleOffset)
.gesture(
DragGesture() /// this never gets called!
.updating($circleOffset) { value, circleOffset, transaction in
circleOffset = value.translation
}
.updating($subviewDragIsActive) {
_, flag, _ in
flag = true
}
)
.padding(50)
}
.background(Color.gray)
.offset(containerOffset)
.simultaneousGesture( /// `simultaneous` is needed to allow dragging from the yellow button
DragGesture() /// move the gray container
.updating($containerOffset) { value, containerOffset, transaction in
containerOffset = value.translation
},
including: subviewDragIsActive ? .subviews : .all
)
}
}
PlaygroundPage.current.setLiveView(ContentView())
If the circle is in a separately-defined View
type, you should use an @State
so you can pass down a Binding
, like this:
import SwiftUI
import PlaygroundSupport
struct CircleView: View {
@GestureState var circleOffset = CGSize.zero
@Binding var dragIsActive: Bool
var body: some View {
Circle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.offset(circleOffset)
.gesture(
DragGesture() /// this never gets called!
.updating($circleOffset) { value, circleOffset, transaction in
circleOffset = value.translation
}
.onChanged { _ in dragIsActive = true }
.onEnded { _ in dragIsActive = false }
)
}
}
struct ContentView: View {
@GestureState var containerOffset = CGSize.zero
@State var subviewDragIsActive = false
var body: some View {
VStack { /// gray container
/// should move the gray container when dragged
Button {
print("Button Pressed")
} label: {
Text("Button")
.padding(50)
.background(Color.yellow)
}
/// should **NOT** move the gray container when dragged
/// How do I make it move the circle instead?
CircleView(dragIsActive: $subviewDragIsActive)
.padding(50)
}
.background(Color.gray)
.offset(containerOffset)
.simultaneousGesture( /// `simultaneous` is needed to allow dragging from the yellow button
DragGesture() /// move the gray container
.updating($containerOffset) { value, containerOffset, transaction in
containerOffset = value.translation
},
including: subviewDragIsActive ? .subviews : .all
)
}
}
PlaygroundPage.current.setLiveView(ContentView())
If any of several subviews might have a drag going on simultaneously (due to multitouch), you should probably use the preference system to pass their isActive states up to ContentView
, but that's a significantly more complex implementation.