A drop-in SwiftUI component for sticky headers with progressive blur — like Apple Music, Photos, and the App Store.
Content scrolls freely underneath the header, becoming increasingly blurred and tinted. Never clipped, always visible.
Building a progressive blur header in SwiftUI is surprisingly hard. There's no native API for variable-radius blur, and most attempts end up with either:
- Hard edges where content gets clipped
- Uniform blur that looks flat
- Complex
UIViewRepresentablewrappers that break with SwiftUI's layout system
AI coding assistants consistently struggle with this pattern — it typically takes many iterations to get right. This package gives you a working solution in one line.
Three-layer ZStack:
| Layer | What | Purpose |
|---|---|---|
| Back | ScrollView |
Content scrolls freely, never clipped |
| Middle | VariableBlurView + gradient |
Progressive blur + tint overlay |
| Front | Your header | Floats above the blur — no opaque background |
The blur uses the same (obfuscated) private API that Apple uses internally, via VariableBlur by nikstar (495+ stars, App Store approved).
Swift Package Manager — add this to your Package.swift:
dependencies: [
.package(url: "https://github.com/dominikmartn/ProgressiveBlurHeader", branch: "main"),
]Or in Xcode: File → Add Package Dependencies → paste the URL.
import ProgressiveBlurHeader
struct MyView: View {
var body: some View {
StickyBlurHeader {
// Your header — NO opaque background!
HStack {
Button("Back") { }
Spacer()
Text("Title").font(.headline)
Spacer()
Button("Settings") { }
}
.padding()
} content: {
// Your scrollable content
ForEach(items) { item in
ItemRow(item: item)
}
}
.background(Color(.systemBackground))
}
}All parameters have sensible defaults — adjust what you need:
StickyBlurHeader(
maxBlurRadius: 5, // Blur intensity: 5 = subtle, 10 = moderate, 20 = strong
fadeExtension: 64, // How far (pt) blur extends below header
tintOpacityTop: 0.7, // Tint at screen top (behind Dynamic Island)
tintOpacityMiddle: 0.5 // Tint at header center
) {
headerView
} content: {
contentView
}| Parameter | Default | Effect |
|---|---|---|
maxBlurRadius |
5 |
Maximum blur at the top edge |
fadeExtension |
64 |
How far blur reaches below the header (pt) |
tintOpacityTop |
0.7 |
Darkening behind Dynamic Island / status bar |
tintOpacityMiddle |
0.5 |
Darkening at the header's vertical center |
The tint automatically adapts to light/dark mode (white tint in light mode, black in dark).
Header must NOT have an opaque background. No
.background(Color.xxx)— theVariableBlurViewIS the background. Adding one hides the blur effect.
Don't clip the content. No
.clipped()anywhere in the hierarchy. Content should always be visible, just blurred.
Header height is measured automatically via
GeometryReader+PreferenceKey. It adapts dynamically if your header changes size.
iOS 26 introduces .safeAreaBar(edge: .top) — a one-liner that adds a sticky bar with blur. But it gives you zero control over blur radius, tint intensity, or fade length. If you need to match a specific design, the custom approach is the way to go.
- iOS 16+
- Swift 5.9+
- VariableBlur by nikstar — the progressive blur engine
- Built by Dominik Martin
MIT — see LICENSE.
