Issue #994
With iOS 18, Apple introduced a powerful new tool for creating beautiful, dynamic backgrounds: MeshGradient
. Forget simple two-color gradients; mesh gradients let you blend a whole grid of colors together smoothly, creating stunning, cloud-like effects.
Think of a mesh gradient as a flexible, colorful net. To create one, you just need to define its structure and colors.
The first step is to define the size of your net. You do this with width
and height
. This tells the gradient how many points your grid will have.
- A
width
of 2 andheight
of 2 gives you a 2x2 grid (4 points total). - A
width
of 3 andheight
of 3 gives you a 3x3 grid (9 points total), like in our fancy counter demo.
Next, you need to tell each color where to sit on the grid. The points
array holds the coordinates for each color. These coordinates are relative, ranging from (0, 0)
(top-left corner) to (1, 1)
(bottom-right corner).
For a 3x3 grid, you’ll provide 9 points, starting from the top-left, moving across each row.
Define colors
This is the fun part! The colors
array is a simple list of all the Color
s you want to use. The number of colors must match the number of points in your grid (width
* height
). The colors are applied to the points in the order you list them.
Mesh as foreground style
Here’s how you’d create a simple, non-moving 2x2 mesh gradient.
struct ForegroundMesh: View {
static let initialPoints: [SIMD2<Float>] = [
.init(x: 0, y: 0), .init(x: 1, y: 0),
.init(x: 0, y: 1), .init(x: 1, y: 1)
]
var body: some View {
VStack {
Image(systemName: "sparkles")
.font(.system(size: 60, weight: .black, design: .rounded))
Text("Hello, Mesh!")
.font(.system(size: 80, weight: .black, design: .rounded))
}
.foregroundStyle(
MeshGradient(
width: 2,
height: 2,
points: Self.initialPoints,
colors: [.purple, .pink, .red, .orange]
)
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(.systemBackground))
}
}
Animating the mesh
We can create mesmerizing, fluid backgrounds by changing the points
over time. The easiest way to do this is with a TimelineView
.
A TimelineView
redraws its content on a schedule, which is perfect for animation. We can use its schedule to constantly calculate new positions for our gradient’s points.
struct TimelineMesh: View {
@State private var points = TimelineMesh.initialPoints
let colors: [Color] = [
.purple, .pink, .red,
.orange, .yellow, .green,
.cyan, .blue, .indigo
]
static let initialPoints: [SIMD2<Float>] = [
// Row 1
.init(x: 0, y: 0), .init(x: 0.5, y: 0), .init(x: 1, y: 0),
// Row 2
.init(x: 0, y: 0.5), .init(x: 0.5, y: 0.5), .init(x: 1, y: 0.5),
// Row 3
.init(x: 0, y: 1), .init(x: 0.5, y: 1), .init(x: 1, y: 1)
]
var body: some View {
TimelineView(.animation) { context in
MeshGradient(
width: 3,
height: 3,
points: points,
colors: colors
)
.ignoresSafeArea()
.onChange(of: context.date) {
withAnimation(.easeInOut(duration: 2.0)) {
updatePoints(time: context.date.timeIntervalSince1970)
}
}
}
}
}
Start the conversation