import SwiftUI
public struct P275_Raining: View {
public init() {}
public var body: some View {
RealisticRain()
.background(
LinearGradient(
colors: [.black.opacity(0.9), .gray.opacity(0.35)],
startPoint: .top,
endPoint: .bottom
)
)
}
}
fileprivate struct RealisticRain: View {
@State private var drops: [RainDropModel] = []
@State private var splashes: [SplashModel] = []
@State private var isRunning = false
var body: some View {
GeometryReader { geo in
ZStack {
ForEach(drops) { drop in
RealisticDrop(drop: drop, screen: geo.size)
}
ForEach(splashes) { splash in
SplashView(splash: splash, screen: geo.size)
}
}
.ignoresSafeArea()
.onAppear {
isRunning = true
spawnRain(in: geo.size)
}
.onDisappear {
isRunning = false
drops.removeAll()
splashes.removeAll()
}
}
}
private func spawnRain(in size: CGSize) {
guard isRunning else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + .random(in: 0.015...0.07)) {
guard isRunning else { return }
let depth = Double.random(in: 0.35...1.25)
let duration = Double.random(in: 0.75...1.85) / depth
let x = Double.random(in: -0.05...1.05)
let wind = Double.random(in: -45...45) * depth
let length = Double.random(in: 28...90) * depth
let width = Double.random(in: 1.0...2.8) * depth
let drop = RainDropModel(
xRatio: x,
yStart: -0.2,
yEnd: 1.15,
wind: wind,
length: length,
width: width,
opacity: min(0.95, 0.25 + depth * 0.55),
blur: max(0, 1.6 - depth),
duration: duration,
depth: depth
)
drops.append(drop)
DispatchQueue.main.asyncAfter(deadline: .now() + duration * 0.92) {
splashes.append(
SplashModel(
xRatio: x + wind / max(size.width, 1),
yRatio: Double.random(in: 0.82...1.03),
scale: depth
)
)
}
DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.15) {
drops.removeAll { $0.id == drop.id }
}
DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.55) {
splashes.removeAll { $0.createdAt < Date().addingTimeInterval(-0.7) }
}
spawnRain(in: size)
}
}
}
fileprivate struct RainDropModel: Identifiable {
let id = UUID()
let xRatio: Double
let yStart: Double
let yEnd: Double
let wind: Double
let length: Double
let width: Double
let opacity: Double
let blur: Double
let duration: Double
let depth: Double
}
fileprivate struct SplashModel: Identifiable {
let id = UUID()
let xRatio: Double
let yRatio: Double
let scale: Double
let createdAt = Date()
}
fileprivate struct RealisticDrop: View {
let drop: RainDropModel
let screen: CGSize
@State private var fall: CGFloat = 0
var body: some View {
Capsule()
.fill(
LinearGradient(
colors: [
.white.opacity(0.9),
.cyan.opacity(0.45),
.blue.opacity(0.08)
],
startPoint: .top,
endPoint: .bottom
)
)
.overlay(
Capsule()
.stroke(.white.opacity(0.55), lineWidth: drop.width * 0.25)
.blur(radius: 0.5)
)
.frame(width: drop.width, height: drop.length)
.shadow(color: .white.opacity(0.25), radius: drop.depth * 2)
.blur(radius: drop.blur)
.opacity(drop.opacity)
.rotationEffect(.degrees(-10 + drop.wind * 0.15))
.position(
x: screen.width * drop.xRatio + CGFloat(drop.wind) * fall,
y: screen.height * (drop.yStart + (drop.yEnd - drop.yStart) * Double(fall))
)
.onAppear {
withAnimation(.linear(duration: drop.duration)) {
fall = 1
}
}
}
}
fileprivate struct SplashView: View {
let splash: SplashModel
let screen: CGSize
@State private var scale: CGFloat = 0.2
@State private var opacity: Double = 0.7
var body: some View {
ZStack {
Ellipse()
.stroke(.cyan.opacity(opacity), lineWidth: 1.2)
.frame(width: 24 * splash.scale, height: 6 * splash.scale)
ForEach(0..<5, id: \.self) { i in
Circle()
.fill(.white.opacity(opacity))
.frame(width: 2.5 * splash.scale, height: 2.5 * splash.scale)
.offset(
x: CGFloat(i - 2) * 7 * splash.scale,
y: CGFloat.random(in: -5...2)
)
}
}
.scaleEffect(scale)
.position(
x: screen.width * splash.xRatio,
y: screen.height * splash.yRatio
)
.onAppear {
withAnimation(.easeOut(duration: 0.45)) {
scale = 1.45
opacity = 0
}
}
}
}
struct P275_Raining_Previews: PreviewProvider {
static var previews: some View {
P275_Raining()
}
}
import SwiftUI
public struct P275_Raining: View {
public init() {}
}
fileprivate struct RealisticRain: View {
@State private var drops: [RainDropModel] = []
@State private var splashes: [SplashModel] = []
@State private var isRunning = false
}
fileprivate struct RainDropModel: Identifiable {
let id = UUID()
let xRatio: Double
let yStart: Double
let yEnd: Double
let wind: Double
let length: Double
let width: Double
let opacity: Double
let blur: Double
let duration: Double
let depth: Double
}
fileprivate struct SplashModel: Identifiable {
let id = UUID()
let xRatio: Double
let yRatio: Double
let scale: Double
let createdAt = Date()
}
fileprivate struct RealisticDrop: View {
let drop: RainDropModel
let screen: CGSize
}
fileprivate struct SplashView: View {
let splash: SplashModel
let screen: CGSize
}
struct P275_Raining_Previews: PreviewProvider {
static var previews: some View {
P275_Raining()
}
}