I'd like to code a mind-map-like visualization of node elements, which might have a variable number and depth of children.
Drawing these elements hierachical using VStack and HStack works pretty well, but I'd like to add a connection between the "parent" and "child" element accordingly (and keep this connection even if I'm dragging one element around).
I've tried using GeometryReader to read the position, but my approach is not really working – especially since I tired to draw a path using an optional position – which always is nil at the time drawing… #stupid
struct TradeNodeMapView: View {
@EnvironmentObject var dataHandler: DataHandler
@Binding var singleTradeNode: TradeNode
@Binding var tradeNodePath: [TradeNode]?
var scrollProxy: ScrollViewProxy
@Binding var selectedNode: TradeNode?
@State var dragOffset = CGSize(width: 0, height: 0)
var body: some View {
VStack(alignment: .center, spacing: 20) {
Text(singleTradeNode.name)
.modifier(NodeViewModifier(nodeSelected: (selectedNode == singleTradeNode ? true : false)))
.coordinateSpace(name: singleTradeNode.hashValue)
.background(
GeometryReader { positionReader in
Color.clear
.onAppear() {
singleTradeNode.nodePosition = CGPoint(x: positionReader.frame(in: .global).midX, y: positionReader.frame(in: .global).midY)
}
}
)
if let children = singleTradeNode.children, !children.isEmpty {
HStack(alignment: .firstTextBaseline, spacing: 20) {
ForEach(children, id: \.hashValue) { singleChild in
TradeNodeMapView(singleTradeNode: singleTradeNode.childBinding(for: singleChild), tradeNodePath: $tradeNodePath, scrollProxy: scrollProxy, selectedNode: $selectedNode)
}
}
}
}
.offset(dragOffset)
.gesture(
LongPressGesture(minimumDuration: 1.0)
.onEnded({ _ in
if selectedNode != singleTradeNode {
selectedNode = singleTradeNode
} else {
selectedNode = nil
}
})
.simultaneously(with: DragGesture()
.onChanged({ drag in
dragOffset = drag.translation
})
.onEnded({ _ in
withAnimation() {
dragOffset = CGSize(width: 0, height: 0)
selectedNode = nil
}
})
)
)
}
}
struct NodeConnectionView: View {
@Binding var singleTradeNode: TradeNode
var body: some View {
ZStack() {
Path { path in
path.move(to: singleTradeNode.nodePosition ?? CGPoint(x: 0, y: 0))
path.addLine(to: singleTradeNode.parent?.nodePosition ?? CGPoint(x: 0, y: 0))
}
.stroke(.yellow, style: StrokeStyle(lineWidth: 3))
}
}
}