# Drawing Trees in SwiftUI

For a new project, we need to draw tree diagrams in SwiftUI. In this post, we'll walk you through our attempts, and show how we use SwiftUI’s preference system to draw clean and interactive diagrams with minimal code.

Our trees have values at the nodes, and any number of children:

```								```struct Tree<A> {
var value: A
var children: [Tree<A>] = []
init(_ value: A, children: [Tree<A>] = []) {
self.value = value
self.children = children
}
}
```
```

For example, here's a simple binary tree that's `Int`-based:

```								```let binaryTree = Tree<Int>(50, children: [
Tree(17, children: [
Tree(12),
Tree(23)
]),
Tree(72, children: [
Tree(54),
Tree(72)
])
])
```
```

As a first step, we can draw the nodes of the tree recursively: for each tree, we create a `VStack` containing the value and its children. The children themselves are drawn using an `HStack`. We require that each element is identifiable so that we can use them with a `ForEach`. Since `Tree` is generic over the node values, we also need to provide a function that turns a node value into a view:

```								```struct DiagramSimple<A: Identifiable, V: View>: View {
let tree: Tree<A>
let node: (A) -> V

var body: some View {
return VStack(alignment: .center) {
node(tree.value)
HStack(alignment: .bottom, spacing: 10) {
ForEach(tree.children, id: \.value.id, content: { child in
DiagramSimple(tree: child, node: self.node)
})
}
}
}
}
```
```

We are almost ready to draw our tree. There is one problem we still have to solve: the integers in our example binary tree do not conform to the `Identifiable` protocol. Rather than conforming a type we don't own (`Int`) to a protocol we don't own (`Identifiable`), we will wrap each integer in the tree in a new object that is identifiable. This will be useful when we want to modify our tree later on; by being able to uniquely identify elements we can have great animations. Here's the extremely simple wrapper class we're going to use:

```								```class Unique<A>: Identifiable {
let value: A
init(_ value: A) { self.value = value }
}
```
```

To transform our tree of type `Tree<Int>` to a tree of type `Tree<Unique<Int>>`, we add `map` to our tree type and use it to wrap each integer within a `Unique` object:

```								```extension Tree {
func map<B>(_ transform: (A) -> B) -> Tree<B> {
Tree<B>(transform(value), children: children.map { \$0.map(transform) })
}
}

let uniqueTree: Tree<Unique<Int>> = binaryTree.map(Unique.init)
```
```

Now we're able to create the diagram view and render a first tree:

```								```struct ContentView: View {
@State var tree = uniqueTree
var body: some View {
DiagramSimple(tree: tree, node: { value in
Text("\(value.value)")
})
}
}
```
```

Our tree looks pretty minimalistic:

To add some styling to the nodes, we create a view modifier that wraps each element view in a frame, adds a white circle with a black stroke as background, and some padding around everything:

```								```struct RoundedCircleStyle: ViewModifier {
func body(content: Content) -> some View {
content
.frame(width: 50, height: 50)
.background(Circle().stroke())
.background(Circle().fill(Color.white))
}
}
```
```

To use this new view modifier we have to change our `ContentView`:

```								```struct ContentView: View {
@State var tree: Tree<Unique<Int>> = binaryTree.map(Unique.init)
var body: some View {
DiagramSimple(tree: tree, node: { value in
Text("\(value.value)")
.modifier(RoundedCircleStyle())
})
}
}
```
```

This is starting to look much better:

However, we're still missing the edges between nodes, so it's hard to see which nodes are connected. To draw these, we need to hook into the layout system. First, we need to collect the center point of each node, and then draw lines from each node's center to its children's centers.

To collect all the center points, we use SwiftUI's preference system. Preferences are the mechanism used to communicate values up the view tree, from children to their ancestors. Any child in the view tree can define a preference, and any ancestor can read that preference.

As a first step, we'll define a new `PreferenceKey` that stores a dictionary. The `PreferenceKey` protocol has two requirements: a default value, in case a subtree doesn't define a preference, and a `reduce` method, that is used to combine the preference values from multiple view subtrees.

```								```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 })
}
}
```
```

In our implementation the default value is an empty dictionary, and the reduce method merges multiple dictionaries into one.

With this preference key in place, we can now use `.anchorPreference` in the diagram view to pass an anchor up the view tree. To use our generic `CollectDict` as preference key, we have to specify the generic parameters of `CollectDict`: `Key` is the node's identifier, and `Value` is `Anchor<CGPoint>` (think of an `Anchor<CGPoint>` as a way to specify a `CGPoint` that can be resolved in the coordinate system of another view later on):

```								```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 {
return 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)
})
}
}
}
}
```
```

Now we can use `backgroundPreferenceValue` to read out all the node centers for our current tree. To resolve the anchors into actual `CGPoint`s, we have to use a `GeometryReader`. We loop over all the children, then draw a line from the center of the current tree's root node to the child node's center:

```								```struct Diagram<A: Identifiable, V: View>: View {
// ...

var body: some View {
VStack(alignment: .center) {
// ...
}.backgroundPreferenceValue(Key.self, { (centers: [A.ID: Anchor<CGPoint>]) 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()
})
}
})
}
}
```
```

`Line` is a custom `Shape` that has absolute `from` and `to` coordinates. We also add both points to the `animatableData`, so that SwiftUI knows how to animate lines (to be able to use `CGPoint` as animatble data, we have to conform it to the `VectorArithmetic` protocol. This conformance is ommitted here for brevity):

```								```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)
}
}
}
```
```

Given all of the machinary above, we finally can use the `Diagram` view and draw a nice tree with edges:

```								```struct ContentView: View {
@State var tree = uniqueTree
var body: some View {
Diagram(tree: tree, node: { value in
Text("\(value.value)")
.modifier(RoundedCircleStyle())
})
}
}
```
```

What's more, our tree supports animations as well. Because we wrapped each element in a `Unique` object, we can animate between different states. For example, when we insert a new number into the tree, SwiftUI can animate that insertion for us:

We have also used this technique to draw different kinds of diagrams. For an upcoming project, we wanted to visualize the structure of SwiftUI's view tree. By using `Mirror` we can access the type of a view's `body` property, which can look like this (for a simple view):

```								```VStack<TupleView<(ModifiedContent<ModifiedContent<ModifiedContent<Button<Text>,_PaddingLayout>,_BackgroundModifier<Color>>,_ClipEffect<RoundedRectangle>>,_ConditionalContent<Text, Text>)>>
```
```

We then parse that into a `Tree<String>`, simplify it slightly, and visualize it using the `Diagram` view above:

Using SwiftUI's built-in features like shapes, gradients, and some padding we were able to draw the above tree with minimal code. It's also really easy to make the trees interactive: you can wrap each node in a `Button`, or add other controls inside the nodes. We've been using this in presentations, to generate static diagrams and to just quickly visualize things.

If you'd like to experiment for yourself, you're welcome to try out the full code for the binary tree, and drawing a tree hierarchy of SwiftUI's view hierarchy.

We add new Swift Talk episodes to our SwiftUI Collection every week. Our latest public episode recreates the iOS Stopwatch app, starting with custom buttons. At over 9 hours, and 24 episodes, we're learning a lot!

To learn with us, become a subscriber.

### SwiftUI

122 Episodes · 43h 35m

## Recent Posts

• ### Book Update: Thinking in SwiftUI

Updated for iOS 17 – final release

• ### Book Update: Thinking in SwiftUI

Updated for iOS 17 – beta

• ### Thinking in SwiftUI: Live Q&A

A Q&A For Our Book Release

• ### Transitions in SwiftUI

Simple but powerful

• ### Aspect Ratios in SwiftUI

A Look Below the Hood