Skip to content

Instantly share code, notes, and snippets.

@PaulWoodIII
Created August 25, 2019 23:55
Show Gist options
  • Save PaulWoodIII/363a06143aef3049c7e422555fb8b070 to your computer and use it in GitHub Desktop.
Save PaulWoodIII/363a06143aef3049c7e422555fb8b070 to your computer and use it in GitHub Desktop.
In SwiftUI use a Geometry Reader to read the position of a child view and draw other views according to the Childs frame
//
// GraphView.swift
// ParentChildLines
//
// Created by Paul Wood on 8/25/19.
// Copyright © 2019 Paul Wood. All rights reserved.
//
import SwiftUI
struct BindViewRectangle: ViewModifier {
let bindingRect: Binding<CGRect?>
let `in`: CoordinateSpace
func body(content: Content) -> some View {
content.background(
GeometryReader { proxy -> AnyView in
let rect = proxy.frame(in: self.`in`)
// This avoids an infinite layout loop
if self.bindingRect.wrappedValue == nil {
DispatchQueue.main.async {
self.bindingRect.wrappedValue = rect.integral
}
}
// Just `EmptyView()` results in the background being ignored, so the size won't be calculated.
return AnyView(EmptyView())
}
)
}
}
extension View {
func bindToRectangle(_ rect :Binding<CGRect?>, in space: CoordinateSpace = .global) -> some View {
self.modifier(BindViewRectangle(bindingRect: rect, in: space))
}
}
struct NodeView: View {
let contentString: String
init(_ string: String) {
self.contentString = string
}
var body: some View {
Text(contentString)
.padding()
.background(
RoundedRectangle(cornerRadius: 8)
.foregroundColor(Color.accentColor.opacity(0.5))
.border(Color.accentColor, width: 3.0)
)
}
}
struct GraphView: View {
// These won't be filled until the first layout pass is complete and will never change
// making this technic insufficiant for animation
@State var topChild: CGRect? = nil
@State var leftChild: CGRect? = nil
@State var rightChild: CGRect? = nil
var body: some View {
GeometryReader { proxy in
ZStack {
VStack(spacing: 30){
NodeView("Top").bindToRectangle(self.$topChild)
HStack(spacing: 100){
NodeView("Left").bindToRectangle(self.$leftChild)
NodeView("Right").bindToRectangle(self.$rightChild)
}
}
Path { path in
guard let topChild = self.topChild,
let leftChild = self.leftChild,
let rightChild = self.rightChild else {
return
}
let topCenterBottom = CGPoint(x: topChild.origin.x + topChild.size.width / 2.0,
y: topChild.origin.y + topChild.size.height)
let leftCenterTop = CGPoint(x: leftChild.origin.x + leftChild.size.width / 2.0,
y: leftChild.origin.y)
let rightCenterTop = CGPoint(x: rightChild.origin.x + rightChild.size.width / 2.0,
y: rightChild.origin.y)
path.move(to: topCenterBottom)
path.addLine(to: leftCenterTop)
path.move(to: topCenterBottom)
path.addLine(to: rightCenterTop)
}.stroke(lineWidth: 3.0).foregroundColor(Color.accentColor)
}
}.edgesIgnoringSafeArea(.top) // global space has these insets and our modifer uses global space
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
GraphView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment