Created
August 25, 2019 23:55
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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