I have an app which has a parent view (graphView
, with a thick blue-border) and a child view (nodeView
, green box). Each has its own SwiftUI DragGesture handler. I want to be able to move both the green node and the blue-bordered graph with different fingers.
GOAL: Keep the “green node” under my finger as I actively drag the “blue-bordered graph.”
PROBLEM: Green node “ghosts”, i.e. seems to bounce around positions.
NOTE:
.simultaneousGesture
on nodeView would let me drag both the graph and the node at the same time with a single finger on the node. But I want to drag the graph and the node with two separate fingers: one on the node, the other on the graph.- some of the UI (eg the
.offset
on theZStack
) is setup to mimic my app's actual setup.
I'm also open to doing this in UIKit, if there's a better way. I suspect the problem is currently from competing 'firing cycles' on the graph vs node gesture handlers.
struct StackOverflowTestView: View {
@State var graphOffset: CGFloat = .zero
@State var graphPreviousOffset: CGFloat = .zero
@State var nodePosition: CGFloat = 200.0
@State var nodePreviousPosition: CGFloat = 200.0
@State var nodeIsDragged = false
@State var lastNodeTranslation: CGFloat = .zero
var graphDrag: some Gesture {
DragGesture()
.onChanged { graphDragValue in
graphOffset = graphPreviousOffset + graphDragValue.translation.height
// If we're simultaneously dragging the node,
// add inverse graph translation to node's position,
// so that node stays under our finger:
if nodeIsDragged {
nodePosition = nodePreviousPosition + lastNodeTranslation - graphDragValue.translation.height
}
}
.onEnded { _ in
graphPreviousOffset = graphOffset
}
}
var nodeDrag: some Gesture {
// .local: node translation is relative to graph's translation,
// e.g. graph +200, node -200 = node translation is -400
// .global: node translation affected by graph's translation
DragGesture(coordinateSpace: SwiftUI.CoordinateSpace.global)
.onChanged { nodeDragValue in
nodeIsDragged = true
lastNodeTranslation = nodeDragValue.translation.height
nodePosition = nodePreviousPosition + nodeDragValue.translation.height
}
.onEnded { _ in
nodeIsDragged = false
lastNodeTranslation = 0
nodePreviousPosition = nodePosition
}
}
var nodeView: some View {
Rectangle()
.fill(.green.opacity(0.9))
.frame(width: 100, height: 100) // NODE SIZE
.position(x: 500, y: nodePosition) // NODE POSITION
.gesture(nodeDrag)
}
var graphView: some View {
ZStack { nodeView }
.border(.blue, width: 32)
.offset(x: 0, y: graphOffset) // GRAPH OFFSET
.background(Rectangle()
.fill(.pink.opacity(0.1))
.frame(width: 1200, height: 800)
.gesture(graphDrag))
}
var body: some View {
graphView.border(.yellow)
}
}