← All shorts
16iOS 18+2 min read

MeshGradient: Real Gradient Meshes in SwiftUI

SwiftUI's MeshGradient renders true multi-point gradient meshes natively, replacing hacky stacks of blurred RadialGradients.

LinearGradient and RadialGradient only interpolate along a line or out from a single center point. Any background that needed several independent color regions blending organically — the kind of look Apple uses in Control Center and widget backgrounds — meant reaching for a custom Metal shader or faking it with layered, blurred shapes.

#The old way

ZStack {
    Color.indigo
    Circle().fill(.purple).blur(radius: 80).offset(x: -100, y: -150)
    Circle().fill(.pink).blur(radius: 80).offset(x: 120, y: 100)
    Circle().fill(.orange).blur(radius: 80).offset(x: -80, y: 150)
}
.ignoresSafeArea()

Approximate, expensive to blur at full screen size, and impossible to animate smoothly without independent state for every circle.

#The new way

import SwiftUI
import Foundation

struct MeshBackground: View {
    var body: some View {
        TimelineView(.animation) { timeline in
            let t = timeline.date.timeIntervalSinceReferenceDate

            MeshGradient(
                width: 3,
                height: 3,
                points: [
                    [0, 0], [0.5, 0], [1, 0],
                    [0, 0.5], [Float(0.5 + 0.15 * sin(t)), Float(0.5 + 0.15 * cos(t))], [1, 0.5],
                    [0, 1], [0.5, 1], [1, 1]
                ],
                colors: [
                    .indigo, .purple, .pink,
                    .blue, .white, .orange,
                    .teal, .green, .yellow
                ]
            )
            .ignoresSafeArea()
        }
    }
}

TimelineView(.animation) redraws every frame, so the center control point drifts in a small circle and the mesh continuously reshapes itself with zero manual animation code.

#Why it matters

  • GPU-driven interpolation between arbitrary control points, not a fixed line or radius
  • Fully declarative and animatable — drive points or colors from state, a TimelineView, or both
  • One view replaces what used to require a custom shader or several blurred, layered shapes

#Gotcha

The points array count must exactly equal width * height — a 3×3 mesh needs exactly 9 points, in row-major order. Get the count wrong and it traps at runtime, not a silent visual glitch. It's also easy to accidentally drag a corner or edge point away from its pinned axis (0, 0.5, 1) while animating the interior — that folds the mesh and produces a visible seam instead of a smooth gradient.

swiftuiios18graphics

Related shorts