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
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 on a Canvas


TimelineView to animate a blob shape change 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
Use a color gradient on the blob shape on a Canvas


Animate a blob shape with color gradient 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
TimelineView to animate a blob shape in the background on a Canvas


Animate a blob shape on a Canvas as a background

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.