Grain Gradient
Soft tri-color noise gradient with film grain rendered via a SwiftUI Metal stitchable shader — two slowly drifting low-frequency value-noise samples blend three colors, and a per-frame high-frequency hash adds film grain
Copies a short instruction with your private download link. Paste it into your agent: it downloads the whole skill (the source code, the setup steps and the mistakes we already made for you) and builds the screen.
Works with
What it does
Full-screen soft gradient with film grain, rendered through SwiftUI's colorEffect Metal pipeline — the poster-style hero background that reads as designed rather than flat. Two low-frequency value-noise samples, offset and scaled differently so they drift independently, drive the two blend weights between three user colors; a contrast exponent shapes those weights from a smooth pastel field to sharper color zones. A high-frequency hash on the raw pixel position, shifted every frame, adds grain that shimmers like real film and stays the same size at any noise scale.
Requires iOS 18+ / macOS 15+.
What you get
- ✓305 lines of source code, ready to paste
- ✓File Layout
- ✓Source Code
- ✓Usage
- ✓Parameters
- ✓Integration Checklist
- ✓Notes / Gotchas
Usage
// Default — warm peach / lilac / rose gradient, full-screen
ZStack {
SWGrainGradient()
.ignoresSafeArea()
// Your content here
}
// Recolor — cool mint
SWGrainGradient(
color1: .teal,
color2: .green,
color3: .mint
)
// As a hero background
heroContent
.background { SWGrainGradient() }
// Clean gradient (no grain), sharper color zones
SWGrainGradient(grain: 0, contrast: 2.0)
// Demo / debug — gear button opens a live-tuning sheet.
// Requires an enclosing NavigationStack.
NavigationStack {
SWGrainGradient(showsControls: true)
}Parameters
| Parameter | Type | Default | Range | Notes |
|---|---|---|---|---|
color1 | Color | #FFB380 (warm peach) | — | Base color, dominant where both noise weights are low |
color2 | Color | #B399FF (soft lilac) | — | Mixed in by the first noise sample |
color3 | Color | #FF8099 (rose) | — | Mixed in by the second noise sample |
speed | Float | 1.0 | 0…3 | Multiplier on the drift time (internally slowed by 0.15) |
scale | Float | 1.2 | 0.2…5 | Spatial scale of the noise field — higher = smaller, more numerous color cells |
grain | Float | 0.06 | 0…0.3 | Film-grain amplitude — 0 = clean gradient, 0.1 ≈ noticeable, 0.2 = chunky photo grain |
contrast | Float | 1.0 | 0.1…4 | Exponent on the blend weights — higher = sharper color transitions, lower = smoother pastel field |
showsControls | Bool | false | — | Demo-only gear ToolbarItem; requires an enclosing NavigationStack |
Integration Checklist
- Copy
SWGrainGradient.swiftandSWGrainGradient.metalinto your Xcode target; the.metalfile is compiled into the defaultShaderLibraryautomatically. - Confirm the deployment target is iOS 18+ / macOS 15+.
- Use it as a
ZStackbackground with.ignoresSafeArea()for full-bleed, or inside.background { }behind a hero section. - Pick three colors close in lightness for the soft poster look; widely different colors read as blotches, so lower
contrastif that happens. - Keep text legible on top: the field drifts, so test your foreground against every region of the gradient (or add a scrim).
Notes / Gotchas
- Grain is in pixel space. The grain hash uses the raw pixel position, not the scaled noise coordinates, so the grain stays film-sized at any
scaleand on any screen size. - The per-frame grain shift is wrapped. The hash input adds
fmod(time * 60.0, 256.0). An unboundedtime * 60grows past float precision inside the hash within minutes and the grain collapses into flat or banded patterns; keep the wrap if you edit the shader. - Square cells on any aspect ratio. Positions are normalized by the shorter side of the bounding rect, so color cells don't stretch on wide or tall views.
- The output is opaque. The shader returns alpha
1, and the base layer iscolor1, so the first frame already matches the gradient tone — no black flash before the shader runs. - Cheap per pixel. Two value-noise samples plus one hash per pixel — far lighter than the FBM-based backgrounds — so a full-screen instance is fine.
- The argument order in the
.colorEffectcall must match the MetalswGrainGradientsignature exactly:boundingRect, time, speed, scale, grain, contrast, color1, color2, color3. - When
showsControlsistrue, the gear button is a nativeToolbarItem— the call site must be inside aNavigationStack.
More in Effects and animation










