Blurred Image Backdrop
A full-screen backdrop made from a blurred image with a tint layer, for placing content on top of it.

- Effects
- iOS 18.0+
- beginner
- Origin
- Original work
- Found by
- blur · backdrop · background · image · album art · tint · photo · desenfoque · fondo · imagen · carátula · tinte · foto
Description
Section titled “Description”A full-screen backdrop made from a blurred image with a tint on top, for placing content over it.
It is decorative, so VoiceOver skips it.
struct BlurredImageBackdropDemo: View { private struct Album { let title: String let artist: String let symbol: String let colors: [Color]
var artwork: Image { Image(size: CGSize(width: 600, height: 600)) { context in let bounds = CGRect(x: 0, y: 0, width: 600, height: 600) context.fill( Path(bounds), with: .linearGradient(Gradient(colors: colors), startPoint: .zero, endPoint: CGPoint(x: 600, y: 600)) )
var symbolImage = context.resolve(Image(systemName: symbol).resizable()) symbolImage.shading = .color(.white.opacity(0.9)) context.draw(symbolImage, in: bounds.insetBy(dx: 170, dy: 170)) } } }
private let albums = [ Album(title: "Solar Tide", artist: "The Longwaves", symbol: "sun.haze.fill", colors: [.orange, .pink]), Album(title: "Night Forest", artist: "Moss & Ember", symbol: "leaf.fill", colors: [.teal, .indigo]), Album(title: "Violet Hours", artist: "Paper Planes", symbol: "moon.stars.fill", colors: [.purple, .blue]), ]
@State private var index = 0 @State private var isAutoPlaying = true @Environment(\.accessibilityReduceMotion) private var reduceMotion
private var album: Album { albums[index] }
var body: some View { ZStack { BlurredImageBackdrop(image: album.artwork) .id(index) .transition(.opacity)
ScrollView { VStack(spacing: 24) { album.artwork .resizable() .scaledToFit() .frame(maxWidth: 260) .clipShape(.rect(cornerRadius: 28)) .shadow(color: .black.opacity(0.35), radius: 24, y: 14) .id(index) .transition(.opacity) .accessibilityHidden(true)
VStack(spacing: 4) { Text(album.title) .font(.system(.title, design: .rounded, weight: .bold))
Text(album.artist) .font(.body) .opacity(0.85) } .multilineTextAlignment(.center)
Button("Next album", systemImage: "forward.fill") { isAutoPlaying = false index = (index + 1) % albums.count } .buttonStyle(.bordered) } .foregroundStyle(.white) .padding(24) .frame(maxWidth: .infinity) } .defaultScrollAnchor(.center, for: .alignment) .tint(.white) } .animation(reduceMotion ? nil : .easeInOut(duration: 0.8), value: index) .task(id: reduceMotion) { guard !reduceMotion else { return }
while isAutoPlaying { try? await Task.sleep(for: .seconds(1.6)) guard !Task.isCancelled, isAutoPlaying else { return } index = (index + 1) % albums.count } } }}API reference
Section titled “API reference”| Parameter | Type | Default |
|---|---|---|
image | Image | Required |
blurRadius | CGFloat | 40 |
tint | Color | .black |
tintOpacity | Double | 0.35 |
Copy this code into your project, or download the file.
// SPDX-License-Identifier: MIT// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
/// A full-screen backdrop made from a blurred image with a tint on top, for placing content over it.////// It is decorative, so VoiceOver skips it.struct BlurredImageBackdrop: View { let image: Image var blurRadius: CGFloat = 40 var tint: Color = .black var tintOpacity: Double = 0.35
var body: some View { Color.clear .overlay { image .resizable() .scaledToFill() .blur(radius: max(blurRadius, 0), opaque: true) } .overlay(tint.opacity(min(max(tintOpacity, 0), 1))) .clipped() .ignoresSafeArea() .accessibilityRepresentation { EmptyView() } }}
// MARK: - Demo
struct BlurredImageBackdropDemo: View { private struct Album { let title: String let artist: String let symbol: String let colors: [Color]
var artwork: Image { Image(size: CGSize(width: 600, height: 600)) { context in let bounds = CGRect(x: 0, y: 0, width: 600, height: 600) context.fill( Path(bounds), with: .linearGradient(Gradient(colors: colors), startPoint: .zero, endPoint: CGPoint(x: 600, y: 600)) )
var symbolImage = context.resolve(Image(systemName: symbol).resizable()) symbolImage.shading = .color(.white.opacity(0.9)) context.draw(symbolImage, in: bounds.insetBy(dx: 170, dy: 170)) } } }
private let albums = [ Album(title: "Solar Tide", artist: "The Longwaves", symbol: "sun.haze.fill", colors: [.orange, .pink]), Album(title: "Night Forest", artist: "Moss & Ember", symbol: "leaf.fill", colors: [.teal, .indigo]), Album(title: "Violet Hours", artist: "Paper Planes", symbol: "moon.stars.fill", colors: [.purple, .blue]), ]
@State private var index = 0 @State private var isAutoPlaying = true @Environment(\.accessibilityReduceMotion) private var reduceMotion
private var album: Album { albums[index] }
var body: some View { ZStack { BlurredImageBackdrop(image: album.artwork) .id(index) .transition(.opacity)
ScrollView { VStack(spacing: 24) { album.artwork .resizable() .scaledToFit() .frame(maxWidth: 260) .clipShape(.rect(cornerRadius: 28)) .shadow(color: .black.opacity(0.35), radius: 24, y: 14) .id(index) .transition(.opacity) .accessibilityHidden(true)
VStack(spacing: 4) { Text(album.title) .font(.system(.title, design: .rounded, weight: .bold))
Text(album.artist) .font(.body) .opacity(0.85) } .multilineTextAlignment(.center)
Button("Next album", systemImage: "forward.fill") { isAutoPlaying = false index = (index + 1) % albums.count } .buttonStyle(.bordered) } .foregroundStyle(.white) .padding(24) .frame(maxWidth: .infinity) } .defaultScrollAnchor(.center, for: .alignment) .tint(.white) } .animation(reduceMotion ? nil : .easeInOut(duration: 0.8), value: index) .task(id: reduceMotion) { guard !reduceMotion else { return }
while isAutoPlaying { try? await Task.sleep(for: .seconds(1.6)) guard !Task.isCancelled, isAutoPlaying else { return } index = (index + 1) % albums.count } } }}
#Preview { BlurredImageBackdropDemo()}