Animate a background with TimelineView in SwiftUI
TimelineView was introduced in iOS 15 to control how to change a view over time. This can be used to animate the background on a Canvas in a SwiftUI.
Create the blob shape
First create a blob shape by adopting the random shape code in Using canvas in
SwiftUI. The shape is composed of 4 points joined by Cubic Bezier curves. The
blobPath
is passed in the containing rectangle and the points as well as the curve
control points are set relative to the size and width of the rectangle.
1struct ContentView: View {
2 body: some View {
3 VStack {
4 Canvas { context, size in
5 context.fill(
6 blobPath(in: CGRect(origin: .zero, size: size)),
7 with: .color(.purple))
8 }
9 .frame(width: 300, height: 300)
10
11 Spacer()
12 }
13 }
14
15 func blobPath(in rect : CGRect) -> Path {
16 let w = rect.width
17 let h = rect.height
18 let path = Path() { path in
19 path.move(to: CGPoint(x: w*0.53, y: h*0.87))
20 path.addCurve(to: CGPoint(x: w*0.15, y: h*0.49) ,
21 control1: CGPoint(x: w*0.06, y: h*0.72),
22 control2: CGPoint(x: w*0.26, y: h*0.75))
23 path.addCurve(to: CGPoint(x: w*0.48, y: h*0.08) ,
24 control1: CGPoint(x: w*0.1, y: h*0.40),
25 control2: CGPoint(x: w*0.17, y: -(h*0.02)))
26 path.addCurve(to: CGPoint(x: w*0.62, y: h*0.48) ,
27 control1: CGPoint(x: w*0.75, y: h*0.18),
28 control2: CGPoint(x: w*0.55, y: h*0.37))
29 path.addCurve(to: CGPoint(x: w*0.53, y: h*0.87) ,
30 control1: CGPoint(x: w*0.87, y: h*0.64),
31 control2: CGPoint(x: w*0.66, y: h*0.89))
32 }
33 return path
34 }
Blob shape on a Canvas
TimeLineView to animate the shape change
The view is wrapped in a TimelineView with an animation schedule similar to those
used in TimelineView to move a shape on a canvas. Angles are calculated that will
oscillate between different values and two distinct values are passed to the
blobPath
function. These offset values are used to modify two points in the path
for the shape. The values to set and which points or control points to adjust are
determined by trial and error. The duration of the animation is set to 4 seconds by
using the remainder(dividingBy:) method on the number of seconds with a
dividingBy parameter of 4.
1struct ContentView: View {
2 var body: some View {
3 TimelineView(.animation) { timeline in
4 VStack {
5 let now = timeline.date.timeIntervalSinceReferenceDate
6 let angle = Angle.degrees(now.remainder(dividingBy: 4)*90)
7 let d = (cos(angle.radians)) * 40
8 let d2 = (cos(angle.radians)) * 10
9
10 Canvas { context, size in
11 context.fill(
12 blobPath(in: CGRect(origin: .zero, size: size),
13 offset1: d,
14 offset2: d2),
15 with: .color(.purple))
16 }
17 .frame(width: 450, height: 400)
18
19 Spacer()
20 }
21 }
22 }
23
24 func blobPath(in rect : CGRect, offset1 d1:Double, offset2 d2:Double) -> Path {
25 let w = rect.width
26 let h = rect.height
27 let path = Path() { path in
28 path.move(to: CGPoint(x: w*0.53, y: h*0.87))
29 path.addCurve(to: CGPoint(x: w*0.15, y: h*0.49) ,
30 control1: CGPoint(x: w*0.06, y: h*0.72),
31 control2: CGPoint(x: w*0.26, y: h*0.75))
32 path.addCurve(to: CGPoint(x: w*0.48, y: h*0.08) ,
33 control1: CGPoint(x: w*0.1, y: h*0.40),
34 control2: CGPoint(x: w*0.17, y: -(h*0.02)))
35 path.addCurve(to: CGPoint(x: w*0.62 - d2, y: h*0.48) ,
36 control1: CGPoint(x: w*0.75, y: h*0.18),
37 control2: CGPoint(x: w*0.55 + d1, y: h*0.37))
38 path.addCurve(to: CGPoint(x: w*0.53, y: h*0.87) ,
39 control1: CGPoint(x: w*0.87 - d2, y: h*0.64),
40 control2: CGPoint(x: w*0.66, y: h*0.89))
41 }
42 return path
43 }
44
45}
TimelineView to animate a blob shape on a Canvas
TimelineView to animate a blob shape change on a Canvas
Add color gradient
The color of the shape is changed to a color gradient and there is more animation by adjusting some of the points on the left of the shape.
1struct ContentView: View {
2 var body: some View {
3 TimelineView(.animation) { timeline in
4 VStack {
5 let now = timeline.date.timeIntervalSinceReferenceDate
6 let angle = Angle.degrees(now.remainder(dividingBy: 4)*90)
7 let d = (cos(angle.radians)) * 40
8 let d2 = (cos(angle.radians)) * 10
9
10 Canvas { context, size in
11 context.fill(
12 blobPath(in: CGRect(origin: .zero, size: size),
13 offset1: d,
14 offset2: d2),
15 with: .linearGradient(Gradient(colors: [.yellow, .purple, .purple]),
16 startPoint: .zero ,
17 endPoint: CGPoint(x: size.width, y: size.height)))
18 }
19 .frame(width: 450, height: 400)
20
21 Spacer()
22 }
23 }
24 }
25
26 func blobPath(in rect : CGRect, offset1 d1:Double, offset2 d2:Double) -> Path {
27 let w = rect.width
28 let h = rect.height
29 let path = Path() { path in
30 path.move(to: CGPoint(x: w*0.53, y: h*0.87))
31 path.addCurve(to: CGPoint(x: w*0.15 + d2, y: h*0.49 - d2 ) ,
32 control1: CGPoint(x: w*0.06, y: h*0.72),
33 control2: CGPoint(x: w*0.26, y: h*0.75))
34 path.addCurve(to: CGPoint(x: w*0.48, y: h*0.08) ,
35 control1: CGPoint(x: w*0.1 + d2, y: h*0.40),
36 control2: CGPoint(x: w*0.17, y: -(h*0.02)))
37 path.addCurve(to: CGPoint(x: w*0.62 - d2, y: h*0.48) ,
38 control1: CGPoint(x: w*0.75, y: h*0.18),
39 control2: CGPoint(x: w*0.55 + d1, y: h*0.37))
40 path.addCurve(to: CGPoint(x: w*0.53, y: h*0.87) ,
41 control1: CGPoint(x: w*0.87 - d2, y: h*0.64),
42 control2: CGPoint(x: w*0.66, y: h*0.89))
43 }
44 return path
45 }
46}
Use a color gradient on the blob shape on a Canvas
Animate a blob shape with color gradient on a Canvas
Use the animated shape behind other content
There are a number of ways to place the animated shape on a canvas behind the App content. One way is to use a ZStack and place the content after the Canvas. The colors in the gradient were changed to be more transparent so the blob seems more in the background.
1struct ContentView: View {
2 var body: some View {
3 TimelineView(.animation) { timeline in
4 ZStack {
5 VStack {
6 let now = timeline.date.timeIntervalSinceReferenceDate
7 let angle = Angle.degrees(now.remainder(dividingBy: 4)*90)
8 let d = (cos(angle.radians)) * 40
9 let d2 = (cos(angle.radians)) * 10
10
11 Canvas { context, size in
12 context.fill(
13 blobPath(in: CGRect(origin: .zero, size: size),
14 offset1: d,
15 offset2: d2),
16 with: .linearGradient(Gradient(colors: [.yellow.opacity(0.5),
17 .purple.opacity(0.4),
18 .purple.opacity(0.5)]),
19 startPoint: .zero ,
20 endPoint: CGPoint(x: size.width, y: size.height)))
21 }
22 .frame(width: 450, height: 400)
23 .scaleEffect(1.5)
24 .offset(x: 50, y: 50)
25
26 Spacer()
27 }
28
29 VStack {
30 Spacer().frame(height:50)
31 Text("Data in the foreground")
32 .font(.system(.title, design:.rounded))
33 .fontWeight(.black)
34 Image(systemName: "character.book.closed.fill")
35 .font(.system(size: 120))
36 .rotation3DEffect(
37 .degrees(20),
38 axis: (x: 1.0, y: 0.0, z: -0.5)
39 )
40 Spacer()
41 }
42 }
43 }
44 }
45
46 func blobPath(in rect : CGRect, offset1 d1:Double, offset2 d2:Double) -> Path {
47 let w = rect.width
48 let h = rect.height
49 let path = Path() { path in
50 path.move(to: CGPoint(x: w*0.53, y: h*0.87))
51 path.addCurve(to: CGPoint(x: w*0.15 + d2, y: h*0.49 - d2 ) ,
52 control1: CGPoint(x: w*0.06, y: h*0.72),
53 control2: CGPoint(x: w*0.26, y: h*0.75))
54 path.addCurve(to: CGPoint(x: w*0.48, y: h*0.08) ,
55 control1: CGPoint(x: w*0.1 + d2, y: h*0.40),
56 control2: CGPoint(x: w*0.17, y: -(h*0.02)))
57 path.addCurve(to: CGPoint(x: w*0.62 - d2, y: h*0.48) ,
58 control1: CGPoint(x: w*0.75, y: h*0.18),
59 control2: CGPoint(x: w*0.55 + d1, y: h*0.37))
60 path.addCurve(to: CGPoint(x: w*0.53, y: h*0.87) ,
61 control1: CGPoint(x: w*0.87 - d2, y: h*0.64),
62 control2: CGPoint(x: w*0.66, y: h*0.89))
63 }
64 return path
65 }
66}
TimelineView to animate a blob shape in the background on a Canvas
Animate a blob shape on a Canvas as a background
Conclusion
This article continues exploring animation of shapes using TimelineView on a canvas. Some of the points of blob shape are changed over time to animate the shape. This can be used to make view appear more active and it can be confined to sections of views by setting the size and location of the canvas.