Skip to main content

How to create custom animated drawings with TimelineView and Canvas

About 3 minSwiftSwiftUIArticle(s)bloghackingwithswift.comcrashcourseswiftswiftuixcodeappstore

How to create custom animated drawings with TimelineView and Canvas 관련

SwiftUI by Example

Back to Home

How to create custom animated drawings with TimelineView and Canvas | SwiftUI by Example

How to create custom animated drawings with TimelineView and Canvas

Updated for Xcode 15

New in iOS 15

SwiftUI’s Canvas view lets us have free control over drawing in our app, and its TimelineView lets us redraw views as often as we need. If we combine these two views together we can create much more advanced effects, including particle systems to simulate rain, snow, fog, fire, and more.

To demonstrate this we can produce some code that creates a simple rain effect.

This starts by defining the data a single drop of rain needs to know in order to work: where it is located horizontally, and when it should be removed from the screen, and how the speed it should fall. That speed property is important, because it means our raindrops won’t all fall at a uniform speed – it makes the whole effect look a lot more realistic.

Alongside a single raindrop, we’re also going to create a class to manage the whole rainstorm. This will have a set of Raindrop instances, and a method that removes any old raindrops, and creates a new one with a random location and speed.

Start with this:

struct Raindrop: Hashable, Equatable {
    var x: Double
    var removalDate: Date
    var speed: Double
}

class Storm: ObservableObject {
    var drops = Set<Raindrop>()

    func update(to date: Date) {
        drops = drops.filter { $0.removalDate > date }
        drops.insert(Raindrop(x: Double.random(in: 0...1), removalDate: date + 1, speed: Double.random(in: 1...2)))
    }
}

There are two important things about that code:

  1. Each rain drop’s X position is a value between 0 and 1, meaning somewhere between the left edge (0) and the right (1).
  2. The removalDate property is set to the current time plus 1 second, so all our raindrops live for 1 second.

Finally, we can create a TimelineView and Canvas. This will:

  1. Use the .animation schedule for the timeline so that it draws as fast as possible.
  2. Call update() on our storm, passing in the current date.
  3. For each rain drop, figure out how much time we have until it is removed.
  4. The drop’s X position is calculated as its x value multiplied by the width of our drawing context. Remember, x will be between 0 and 1, so this will produce a value between 0 and the width of our drawing context.
  5. The drop’s Y position will be its age multiplied by its speed, multiplied by the height of the canvas. If we left it there our drops would “fall” upwards, but we subtract that from the canvas height they will fall downwards.
  6. We can now fill a thin and long capsule at that X and Y coordinate, with a suitably rainy color.

Here’s that in code:

struct ContentView: View {
    @StateObject private var storm = Storm()
    let rainColor = Color(red: 0.25, green: 0.5, blue: 0.75)

    var body: some View {
        TimelineView(.animation) { timeline in
            Canvas { context, size in
                storm.update(to: timeline.date)

                for drop in storm.drops {
                    let age = timeline.date.distance(to: drop.removalDate)
                    let rect = CGRect(x: drop.x * size.width, y: size.height - (size.height * age * drop.speed), width: 2, height: 10)
                    let shape = Capsule().path(in: rect)
                    context.fill(shape, with: .color(rainColor))
                }
            }
        }
        .background(.black)
        .ignoresSafeArea()
    }
}

That’s not a huge amount of code, but it already creates a pretty compelling effect. From here you can start to consider things like having different opacities for raindrops, placing them at a slight angle, and more.

Over on Hacking with Swift+ I have a whole series of tutorials teaching you how to recreate the particle effects in Apple’s Weather app using Canvas and TimelineView: open in new window.

Similar solutions…
SwiftUI tips and tricks | SwiftUI by Example

SwiftUI tips and tricks
All SwiftUI property wrappers explained and compared | SwiftUI by Example

All SwiftUI property wrappers explained and compared
How to add Metal shaders to SwiftUI views using layer effects | SwiftUI by Example

How to add Metal shaders to SwiftUI views using layer effects
How to use Instruments to profile your SwiftUI code and identify slow layouts | SwiftUI by Example

How to use Instruments to profile your SwiftUI code and identify slow layouts
Building a menu using List | SwiftUI by Example

Building a menu using List

이찬희 (MarkiiimarK)
Never Stop Learning.