Updated for Xcode 14.2
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:
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:
.animation
schedule for the timeline so that it draws as fast as possible.update()
on our storm, passing in the current date.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.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
: https://www.hackingwithswift.com/plus/remaking-apps.
SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.