-
-
Save floriankugler/d83f29b6f9cd39667ecc204fc5b88ea8 to your computer and use it in GitHub Desktop.
Trees
This file contains 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
// | |
// 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<A> { | |
var value: A | |
var children: [Tree<A>] = [] | |
init(_ value: A, children: [Tree<A>] = []) { | |
self.value = value | |
self.children = children | |
} | |
} | |
extension Tree { | |
func map<B>(_ transform: (A) -> B) -> Tree<B> { | |
return Tree<B>(transform(value), children: children.map({ $0.map(transform) })) | |
} | |
} | |
class Unique<A>: Identifiable { | |
let value: A | |
init(_ value: A) { self.value = value } | |
} | |
struct CollectDict<Key: Hashable, Value>: 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<CGPoint, CGPoint> { | |
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<A: Identifiable, V: View>: View { | |
let tree: Tree<A> | |
let node: (A) -> V | |
typealias Key = CollectDict<A.ID, Anchor<CGPoint>> | |
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<CGPoint>]) 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() | |
}) | |
} | |
}) | |
} | |
} |
This file contains 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
// | |
// ContentView.swift | |
// DiagramsSample | |
// | |
// Created by Chris Eidhof on 16.12.19. | |
// Copyright © 2019 objc.io. All rights reserved. | |
// | |
import SwiftUI | |
let binaryTree = Tree<Int>(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<Int> { | |
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() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment