Skip to content
{{ message }}

Instantly share code, notes, and snippets.

chriseidhof/diagrams.swift

Last active Aug 4, 2020
Trees
 // // Diagrams.swift // DiagramsSample // // Created by Chris Eidhof on 16.12.19. // Copyright © 2019 objc.io. All rights reserved. // import SwiftUI /// A simple Tree datastructure that holds nodes with `A` as the value. struct Tree { var value: A var children: [Tree] = [] init(_ value: A, children: [Tree] = []) { self.value = value self.children = children } } extension Tree { func map(_ transform: (A) -> B) -> Tree { return Tree(transform(value), children: children.map({ \$0.map(transform) })) } } class Unique: Identifiable { let value: A init(_ value: A) { self.value = value } } struct CollectDict: PreferenceKey { static var defaultValue: [Key:Value] { [:] } static func reduce(value: inout [Key:Value], nextValue: () -> [Key:Value]) { value.merge(nextValue(), uniquingKeysWith: { \$1 }) } } /// This is needed to use `CGPoint` as animatable data extension CGPoint: VectorArithmetic { public static func -= (lhs: inout CGPoint, rhs: CGPoint) { lhs = lhs - rhs } public static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint { return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) } public static func += (lhs: inout CGPoint, rhs: CGPoint) { lhs = lhs + rhs } public mutating func scale(by rhs: Double) { x *= CGFloat(rhs) y *= CGFloat(rhs) } public static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) } public var magnitudeSquared: Double { return Double(x*x + y*y) } } /// Draws an edge from `from` to `to` struct Line: Shape { var from: CGPoint var to: CGPoint var animatableData: AnimatablePair { get { AnimatablePair(from, to) } set { from = newValue.first to = newValue.second } } func path(in rect: CGRect) -> Path { Path { p in p.move(to: self.from) p.addLine(to: self.to) } } } /// A simple Diagram. It's not very performant yet, but works great for smallish trees. struct Diagram: View { let tree: Tree let node: (A) -> V typealias Key = CollectDict> var body: some View { VStack(alignment: .center) { node(tree.value) .anchorPreference(key: Key.self, value: .center, transform: { [self.tree.value.id: \$0] }) HStack(alignment: .bottom, spacing: 10) { ForEach(tree.children, id: \.value.id, content: { child in Diagram(tree: child, node: self.node) }) } }.backgroundPreferenceValue(Key.self, { (centers: [A.ID: Anchor]) in GeometryReader { proxy in ForEach(self.tree.children, id: \.value.id, content: { child in Line( from: proxy[centers[self.tree.value.id]!], to: proxy[centers[child.value.id]!]) .stroke() }) } }) } }
 // // ContentView.swift // DiagramsSample // // Created by Chris Eidhof on 16.12.19. // Copyright © 2019 objc.io. All rights reserved. // import SwiftUI let binaryTree = Tree(50, children: [ Tree(17, children: [ Tree(12), Tree(23) ]), Tree(72, children: [ Tree(54), Tree(72) ]) ]) let uniqueTree = binaryTree.map(Unique.init) struct RoundedCircleStyle: ViewModifier { func body(content: Content) -> some View { content .frame(width: 50, height: 50) .background(Circle().stroke()) .background(Circle().fill(Color.white)) .padding(10) } } extension Tree where A == Unique { mutating func insert(_ number: Int) { if number < value.value { if children.count > 0 { children[0].insert(number) } else { children.append(Tree(Unique(number))) } } else { if children.count == 2 { children[1].insert(number) } else if children.count == 1 && children[0].value.value > number { children[0].insert(number) } else { children.append(Tree(Unique(number))) } } } } struct BinaryTreeView: View { @State var tree = uniqueTree var body: some View { VStack { Button(action: { withAnimation(.default) { self.tree.insert(Int.random(in: 0...100)) } }, label: { Text("Insert random number") }) Diagram(tree: tree, node: { value in Text("\(value.value)") .modifier(RoundedCircleStyle()) }) } } } struct ContentView: View { var body: some View { BinaryTreeView() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }

dndydon commented Jan 1, 2020 • edited

 Chris, do you know why I get error "Redundant conformance of 'CGPoint' to protocol 'VectorArithmetic'" on... ``````/// This is needed to use `CGPoint` as animatable data extension CGPoint: VectorArithmetic { `````` ?

dndydon commented Jan 1, 2020

 I found and fixed the problem: When I added your Gist as a file "Diagram.swift" in my project, I should not have included it in the test target. The test target was the source of the redundant conformance. A better error message is needed when this happens.

happyjem commented Feb 2, 2020

 I suggest the code below in RoundedCircleStyle Struct ```struct RoundedCircleStyle: ViewModifier { func body(content: Content) -> some View { content .frame(width: 50, height: 50) .background(Circle().stroke()) .background(Circle().fill(Color.white)) .foregroundColor(Color.black) // #. add foreground code .padding(10) } }```
to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.