
How to Create Amazing Animations with Recursion in SwiftUI — Fractal Tree, Koch Snowflake, and…
Koch Snowflake
Recursion is a technique that allows us to solve complex problems by breaking them down into smaller and simpler subproblems. Recursion is when a function calls itself, either directly or indirectly, to solve a smaller version of the same problem.
Recursion can be very useful for creating animations in SwiftUI. Animations can make our apps more interactive, engaging, and delightful. Animations can also help us visualize how recursion works by showing us the steps of the recursive process, the changes in the data structures, and the flow of control.
In this article, we will explore some examples of animations using recursion in SwiftUI, and learn how to create our own animations with some simple tools and techniques. Let’s get started!
Example 1: Recursive Fractal Tree
The first example we will look at is a recursive fractal tree. A fractal tree is a tree where each branch splits into two or more smaller branches, and this process repeats until a certain condition is met. A fractal tree is an example of a self-similar pattern, which means that it looks the same at different scales.
Here is a possible animation of a recursive fractal tree in SwiftUI:
In this animation, each branch represents a recursive function call. The length and angle of each branch are determined by some parameters that change with each level of recursion. The animation shows how the function calls itself with different parameters until it reaches a minimum branch length, and then stops.
To create this animation in SwiftUI, we can use a custom view called RecursiveTree that takes four parameters: length, angle, depth, and color. The length is the initial branch length, the angle is the initial branch angle, the depth is the maximum level of recursion, and the color is the branch color.
The RecursiveTree view has a body property that returns a ZStack of two views: a Rectangle that represents the current branch, and an HStack that contains two RecursiveTree views that represent the left and right sub-branches. The Rectangle has a frame with the given length and width, and a rotation effect with the given angle. The HStack has a spacing of zero and an alignment of bottom. The RecursiveTree views inside the HStack have their length multiplied by a factor (0.7), their angle increased or decreased by an amount (45 degrees), their depth decreased by one, and their color darkened by an amount (0.1).
The RecursiveTree view also has an animate property that returns an Animation object that controls the animation duration and delay. The animation duration is inversely proportional to the depth (the deeper the level, the shorter the duration), and the animation delay is proportional to the depth (the deeper the level, the longer the delay). This creates a cascading effect where the branches grow from top to bottom.
Here is the code for the RecursiveTree view in SwiftUI:
import SwiftUI
struct RecursiveTree: View {
// Define some constants
let lengthFactor: CGFloat \= 0.7 // The factor by which branch length decreases
let angleFactor: CGFloat \= 45 // The angle by which branch angle changes
let colorFactor: Double \= 0.1 // The factor by which branch color darkens
// Define some parameters
let length: CGFloat // The initial branch length
let angle: CGFloat // The initial branch angle
let depth: Int // The maximum level of recursion
let color: Color // The branch color
// Define some computed properties
var body: some View {
ZStack {
// Draw the current branch as a rectangle
Rectangle()
.frame(width: 10, height: length)
.foregroundColor(color)
.rotationEffect(.degrees(Double(angle)))
// Draw the left and right sub-branches as recursive trees
HStack(spacing: 0) {
if depth > 0 {
RecursiveTree(
length: length * lengthFactor,
angle: angle - angleFactor,
depth: depth - 1,
color: color.opacity(1 - colorFactor)
)
}
Spacer()
if depth > 0 {
RecursiveTree(
length: length * lengthFactor,
angle: angle + angleFactor,
depth: depth - 1,
color: color.opacity(1 - colorFactor)
)
}
}
.frame(height: length)
.alignmentGuide(.bottom, computeValue: { _ in 0 })
}
.animation(animate) // Apply the animation
}
var animate: Animation { // Define the animation duration and delay let duration = 1 / Double(depth + 1) let delay = Double(depth) * 0.1
// Return an animation with the given duration and delay
return Animation.linear(duration: duration).delay(delay)} }
struct RecursiveTree_Previews: PreviewProvider { static var previews: some View { RecursiveTree(length: 200, angle: 90, depth: 6, color: .green) } }
To use this view in our app, we can simply create an instance of RecursiveTree with some initial parameters and place it inside a VStack with a Spacer. For example:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Spacer()
RecursiveTree(length: 200, angle: 90, depth: 6, color: .green)
}
}
}
Example 2: Recursive Koch Snowflake
The second example we will look at is a recursive Koch snowflake. A Koch snowflake is a fractal curve that is constructed by repeatedly replacing each line segment with four smaller segments that form a “bump”. A Koch snowflake is an example of a self-similar pattern, which means that it looks the same at different scales.
Here is a possible animation of a recursive Koch snowflake in SwiftUI:
In this animation, each line segment represents a recursive function call. The length and angle of each line segment are determined by some parameters that change with each level of recursion. The animation shows how the function calls itself with different parameters until it reaches a minimum line length, and then stops.
To create this animation in SwiftUI, we can use a custom view called RecursiveSnowflake that takes four parameters: length, angle, depth, and color. The length is the initial line length, the angle is the initial line angle, the depth is the maximum level of recursion, and the color is the line color.
The RecursiveSnowflake view has a body property that returns a Path that draws the Koch snowflake curve. The Path has a move(to:) method that moves to the starting point of the curve, and a recursive method called kochCurve(length:angle:depth:) that draws each line segment of the curve. The kochCurve(length:angle:depth:) method has three cases:
- If depth is zero, draw a straight line with the given length and angle
- If depth is one, draw four smaller lines with different angles that form a “bump”
- Otherwise, call kochCurve(length:angle:depth:) recursively with smaller length and depth for each of the four smaller lines
The RecursiveSnowflake view also has an animate property that returns an Animation object that controls the animation duration and delay. The animation duration is inversely proportional to the depth (the deeper the level, the shorter the duration), and the animation delay is proportional to the depth (the deeper the level, the longer the delay). This creates a cascading effect where the lines grow from top to bottom.
Here is the code for the RecursiveSnowflake view in SwiftUI:
import SwiftUI
struct RecursiveSnowflake: View {
// Define some constants
let lengthFactor: CGFloat \= 1 / 3 // The factor by which line length decreases
let angleFactor: CGFloat \= 60 // The angle by which line angle changes
// Define some parameters
let length: CGFloat // The initial line length
let angle: CGFloat // The initial line angle
let depth: Int // The maximum level of recursion
let color: Color // The line color
// Define some computed properties
var body: some View {
Path { path in
// Move to the starting point of the curve
path.move(to: CGPoint(x: length / 2 * cos(angle), y: length / 2 * sin(angle)))
// Draw the Koch snowflake curve
kochCurve(length: length, angle: angle + .pi / 3 * 2, depth: depth, in: path)
kochCurve(length: length, angle: angle + .pi / 3 * -2, depth: depth, in: path)
kochCurve(length: length, angle: angle + .pi, depth: depth, in: path)
}
.stroke(color, lineWidth: 2) // Set the stroke color and width
.animation(animate) // Apply the animation}
func kochCurve(length: CGFloat, angle: CGFloat, depth: Int, in path: inout Path) { // Draw a Koch snowflake curve segment with the given length, angle, and depth switch depth { case 0: // If depth is zero, draw a straight line path.addLine(to: CGPoint(x: length * cos(angle), y: length * sin(angle))) case 1: // If depth is one, draw four smaller lines that form a "bump" kochCurve(length: length * lengthFactor, angle: angle, depth: 0, in: &path) kochCurve(length: length * lengthFactor, angle: angle + .pi / 3, depth: 0, in: &path) kochCurve(length: length * lengthFactor, angle: angle - .pi / 3, depth: 0, in: &path) kochCurve(length: length * lengthFactor, angle: angle, depth: 0, in: &path) default: // Otherwise, call kochCurve recursively for each of the four smaller lines kochCurve(length: length * lengthFactor, angle: angle, depth: depth - 1, in: &path) kochCurve(length: length * lengthFactor, angle: angle + .pi / 3, depth: depth - 1, in: &path) kochCurve(length: length * lengthFactor, angle: angle - .pi / 3, depth: depth - 1, in: &path) kochCurve(length: length * lengthFactor, angle: angle, depth: depth - 1, in: &path) } }
var animate: Animation { // Define the animation duration and delay let duration = 1 / Double(depth + 1) let delay = Double(depth) * 0.1
// Return an animation with the given duration and delay
return Animation.linear(duration: duration).delay(delay)
} }
struct RecursiveSnowflake_Previews: PreviewProvider { static var previews: some View { RecursiveSnowflake(length: 200, angle: .pi / 2 * -1 , depth: 5 , color : .blue) } }
To use this view in our app, we can simply create an instance of RecursiveSnowflake with some initial parameters and place it inside a VStack with a Spacer. For example:
import SwiftUI
struct ContentView : View {
var body : some View {
VStack {
Spacer()
RecursiveSnowflake(length : 200 , angle : .pi / 2 * -1 , depth : 5 , color : .blue)
}
}
}
Example 3 : Recursive Sierpinski Triangle
The third example we will look at is a recursive Sierpinski triangle. A Sierpinski triangle is a fractal shape that is constructed by repeatedly removing the middle triangle from each equilateral triangle. A Sierpinski triangle is an example of a self-similar pattern , which means that it looks the same at different scales.
Here is a possible animation of a recursive Sierpinski triangle in SwiftUI :
In this animation , each triangle represents a recursive function call. The size and position of each triangle are determined by some parameters that change with each level of recursion. The animation shows how the function calls itself with different parameters until it reaches a minimum triangle size , and then stops.
To create this animation in SwiftUI , we can use a custom view called RecursiveTriangle that takes four parameters : size , offset , depth , and color. The size is the initial triangle size , the offset is the initial triangle offset from the center , the depth is the maximum level of recursion , and the color is the triangle color.
The RecursiveTriangle view has a body property that returns a ZStack of three views : a Triangle that represents the current triangle , and two RecursiveTriangle views that represent the top and bottom sub-triangles. The Triangle has a frame with the given size and offset , and a fill with the given color. The RecursiveTriangle views have their size divided by two , their offset adjusted by half of the size, and their depth decreased by one.
The RecursiveTriangle view also has an animate property that returns an Animation object that controls the animation duration and delay. The animation duration is inversely proportional to the depth (the deeper the level, the shorter the duration), and the animation delay is proportional to the depth (the deeper the level, the longer the delay). This creates a cascading effect where the triangles shrink from top to bottom.
Here is the code for the RecursiveTriangle view in SwiftUI:
import SwiftUI
struct RecursiveTriangle: View {
// Define some constants
let sizeFactor: CGFloat = 0.5 // The factor by which triangle size decreases
let offsetFactor: CGFloat = 0.25 // The factor by which triangle offset changes
// Define some parameters
let size: CGSize // The initial triangle size
let offset: CGSize // The initial triangle offset from the center
let depth: Int // The maximum level of recursion
let color: Color // The triangle color
// Define some computed properties
var body: some View {
ZStack {
// Draw the current triangle
Triangle()
.frame(width: size.width, height: size.height)
.offset(x: offset.width, y: offset.height)
.fill(color)
// Draw the top and bottom sub-triangles as recursive triangles
if depth > 0 {
RecursiveTriangle(
size: CGSize(width: size.width * sizeFactor, height: size.height * sizeFactor),
offset: CGSize(width: offset.width, height: offset.height - size.height * offsetFactor),
depth: depth - 1,
color: color.opacity(1 - Double(depth) * 0.1)
)
RecursiveTriangle(
size: CGSize(width: size.width * sizeFactor, height: size.height * sizeFactor),
offset: CGSize(width: offset.width, height: offset.height + size.height * offsetFactor),
depth: depth - 1,
color: color.opacity(1 - Double(depth) * 0.1)
)
}
}
.animation(animate) // Apply the animation
}
var animate: Animation {
// Define the animation duration and delay
let duration = 1 / Double(depth + 1)
let delay = Double(depth) * 0.1
// Return an animation with the given duration and delay
return Animation.linear(duration: duration).delay(delay)
}
}
struct RecursiveTriangle_Previews: PreviewProvider {
static var previews: some View {
RecursiveTriangle(size: CGSize(width: 200, height: 200), offset: CGSize.zero, depth: 5, color: .red)
}
}
To use this view in our app, we can simply create an instance of RecursiveTriangle with some initial parameters and place it inside a VStack with a Spacer. For example:
import SwiftUI
struct ContentView : View {
var body : some View {
VStack {
Spacer()
RecursiveTriangle(size : CGSize(width : 200 , height : 200) , offset : CGSize.zero , depth : 5 , color : .red)
}
}
}
Conclusion
In this article, we have seen some examples of animations using recursion in SwiftUI, and learned how to create our own animations with some simple tools and techniques. We have also learned some basic concepts of recursion, such as recursive definition, base case, recursive case, call stack, and time complexity.
Recursion is a powerful technique that can help us solve complex problems with elegant code. However, recursion can also be challenging to understand and debug. Animations can help us visualize recursion and make it more fun and engaging.
I hope you enjoyed this article and learned something new. If you have any questions or feedback, please let me know in the comments below. Thank you for reading!