progressive-blur-header-swiftui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseProgressiveBlurHeader SwiftUI Skill
ProgressiveBlurHeader SwiftUI Skill
Skill by ara.so — Daily 2026 Skills collection.
A drop-in SwiftUI package for sticky headers with progressive (variable-radius) blur — replicating the Apple Music, Photos, and App Store style where content scrolls underneath the header with increasing blur and tint. No clipping, no hard edges.
由ara.so提供的Skill — 属于Daily 2026 Skills合集。
这是一个即插即用的SwiftUI包,用于实现带有渐进式(可变半径)模糊效果的粘性头部,还原了Apple Music、Photos和App Store的样式:内容在头部下方滚动时,模糊效果和色调会逐渐增强。无裁剪、无生硬边缘。
Installation
安装
Swift Package Manager (Xcode)
Swift Package Manager(Xcode)
File → Add Package Dependencies → paste:
https://github.com/dominikmartn/ProgressiveBlurHeaderSelect branch:
main文件 → 添加包依赖 → 粘贴:
https://github.com/dominikmartn/ProgressiveBlurHeader选择分支:
mainPackage.swift
Package.swift
swift
dependencies: [
.package(url: "https://github.com/dominikmartn/ProgressiveBlurHeader", branch: "main"),
]Then add to your target:
swift
.target(
name: "YourApp",
dependencies: ["ProgressiveBlurHeader"]
)Requirements: iOS 16+, Swift 5.9+
swift
dependencies: [
.package(url: "https://github.com/dominikmartn/ProgressiveBlurHeader", branch: "main"),
]然后添加到你的目标:
swift
.target(
name: "YourApp",
dependencies: ["ProgressiveBlurHeader"]
)要求: iOS 16+,Swift 5.9+
Core API
核心API
StickyBlurHeader
StickyBlurHeaderStickyBlurHeader
StickyBlurHeaderThe primary component. Takes two closures: and .
ViewBuilderheadercontentswift
StickyBlurHeader(
maxBlurRadius: Double, // Default: 5
fadeExtension: CGFloat, // Default: 64
tintOpacityTop: Double, // Default: 0.7
tintOpacityMiddle: Double // Default: 0.5
) {
// header view (NO opaque background)
} content: {
// scrollable content
}核心组件。接收两个闭包:和。
ViewBuilderheadercontentswift
StickyBlurHeader(
maxBlurRadius: Double, // 默认值:5
fadeExtension: CGFloat, // 默认值:64
tintOpacityTop: Double, // 默认值:0.7
tintOpacityMiddle: Double // 默认值:0.5
) {
// 头部视图(不要设置不透明背景)
} content: {
// 可滚动内容
}Parameters
参数说明
| Parameter | Type | Default | Description |
|---|---|---|---|
| | | Max blur at the top edge. 5 = subtle, 10 = moderate, 20 = strong |
| | | Points below the header the blur extends |
| | | Tint behind Dynamic Island / status bar |
| | | Tint at the header's vertical center |
The tint color adapts automatically to light/dark mode.
| 参数 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| | | 顶部边缘的最大模糊半径。5=柔和,10=中等,20=强烈 |
| | | 模糊效果延伸到头部下方的点数 |
| | | 动态岛/状态栏后方的色调透明度 |
| | | 头部垂直中心位置的色调透明度 |
色调颜色会自动适配亮色/暗色模式。
Basic Usage
基础用法
swift
import SwiftUI
import ProgressiveBlurHeader
struct ContentView: View {
let items = (1...50).map { "Item \($0)" }
var body: some View {
StickyBlurHeader {
// Header — NO opaque background
HStack {
Button("Back") { }
Spacer()
Text("Library").font(.headline)
Spacer()
Button("Settings") { }
}
.padding()
} content: {
ForEach(items, id: \.self) { item in
Text(item)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(8)
.padding(.horizontal)
}
}
.background(Color(.systemBackground))
}
}swift
import SwiftUI
import ProgressiveBlurHeader
struct ContentView: View {
let items = (1...50).map { "Item \($0)" }
var body: some View {
StickyBlurHeader {
// 头部 — 不要设置不透明背景
HStack {
Button("Back") { }
Spacer()
Text("Library").font(.headline)
Spacer()
Button("Settings") { }
}
.padding()
} content: {
ForEach(items, id: \.self) { item in
Text(item)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(8)
.padding(.horizontal)
}
}
.background(Color(.systemBackground))
}
}Common Patterns
常见模式
Navigation-Style Header with Back Button
带返回按钮的导航式头部
swift
import SwiftUI
import ProgressiveBlurHeader
struct AlbumDetailView: View {
let albumTitle: String
@Environment(\.dismiss) private var dismiss
var body: some View {
StickyBlurHeader(
maxBlurRadius: 8,
fadeExtension: 80,
tintOpacityTop: 0.75,
tintOpacityMiddle: 0.55
) {
HStack {
Button {
dismiss()
} label: {
Image(systemName: "chevron.left")
.fontWeight(.semibold)
}
Spacer()
Text(albumTitle)
.font(.headline)
.lineLimit(1)
Spacer()
Button {
// action
} label: {
Image(systemName: "ellipsis.circle")
}
}
.padding(.horizontal)
.padding(.vertical, 12)
} content: {
LazyVStack(spacing: 0) {
ForEach(0..<30) { index in
SongRow(index: index)
Divider().padding(.leading)
}
}
}
.background(Color(.systemBackground))
}
}swift
import SwiftUI
import ProgressiveBlurHeader
struct AlbumDetailView: View {
let albumTitle: String
@Environment(\.dismiss) private var dismiss
var body: some View {
StickyBlurHeader(
maxBlurRadius: 8,
fadeExtension: 80,
tintOpacityTop: 0.75,
tintOpacityMiddle: 0.55
) {
HStack {
Button {
dismiss()
} label: {
Image(systemName: "chevron.left")
.fontWeight(.semibold)
}
Spacer()
Text(albumTitle)
.font(.headline)
.lineLimit(1)
Spacer()
Button {
// 操作
} label: {
Image(systemName: "ellipsis.circle")
}
}
.padding(.horizontal)
.padding(.vertical, 12)
} content: {
LazyVStack(spacing: 0) {
ForEach(0..<30) { index in
SongRow(index: index)
Divider().padding(.leading)
}
}
}
.background(Color(.systemBackground))
}
}Subtle Blur (Apple Photos Style)
柔和模糊(Apple Photos风格)
swift
StickyBlurHeader(
maxBlurRadius: 5,
fadeExtension: 56,
tintOpacityTop: 0.6,
tintOpacityMiddle: 0.4
) {
HStack {
Text("Photos")
.font(.largeTitle)
.fontWeight(.bold)
Spacer()
Button {
// action
} label: {
Image(systemName: "plus")
}
}
.padding(.horizontal)
.padding(.top, 8)
.padding(.bottom, 12)
} content: {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 2) {
ForEach(photos) { photo in
PhotoThumbnail(photo: photo)
}
}
.padding(2)
}
.background(Color(.systemBackground))swift
StickyBlurHeader(
maxBlurRadius: 5,
fadeExtension: 56,
tintOpacityTop: 0.6,
tintOpacityMiddle: 0.4
) {
HStack {
Text("Photos")
.font(.largeTitle)
.fontWeight(.bold)
Spacer()
Button {
// 操作
} label: {
Image(systemName: "plus")
}
}
.padding(.horizontal)
.padding(.top, 8)
.padding(.bottom, 12)
} content: {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))], spacing: 2) {
ForEach(photos) { photo in
PhotoThumbnail(photo: photo)
}
}
.padding(2)
}
.background(Color(.systemBackground))Strong Blur (App Store Style)
强烈模糊(App Store风格)
swift
StickyBlurHeader(
maxBlurRadius: 12,
fadeExtension: 72,
tintOpacityTop: 0.85,
tintOpacityMiddle: 0.6
) {
VStack(spacing: 4) {
HStack {
Text("Today")
.font(.largeTitle)
.fontWeight(.bold)
Spacer()
Button {
// profile action
} label: {
Image(systemName: "person.crop.circle.fill")
.font(.title2)
}
}
HStack {
Text(Date(), style: .date)
.font(.subheadline)
.foregroundStyle(.secondary)
.textCase(.uppercase)
Spacer()
}
}
.padding(.horizontal)
.padding(.vertical, 12)
} content: {
LazyVStack(spacing: 16) {
ForEach(featuredApps) { app in
AppCard(app: app)
}
}
.padding()
}
.background(Color(.systemBackground))swift
StickyBlurHeader(
maxBlurRadius: 12,
fadeExtension: 72,
tintOpacityTop: 0.85,
tintOpacityMiddle: 0.6
) {
VStack(spacing: 4) {
HStack {
Text("Today")
.font(.largeTitle)
.fontWeight(.bold)
Spacer()
Button {
// 个人资料操作
} label: {
Image(systemName: "person.crop.circle.fill")
.font(.title2)
}
}
HStack {
Text(Date(), style: .date)
.font(.subheadline)
.foregroundStyle(.secondary)
.textCase(.uppercase)
Spacer()
}
}
.padding(.horizontal)
.padding(.vertical, 12)
} content: {
LazyVStack(spacing: 16) {
ForEach(featuredApps) { app in
AppCard(app: app)
}
}
.padding()
}
.background(Color(.systemBackground))Dynamic Header (Height Changes)
动态头部(高度变化)
Header height is measured automatically via + — no manual sizing needed:
GeometryReaderPreferenceKeyswift
StickyBlurHeader {
VStack(alignment: .leading, spacing: 4) {
Text("Title").font(.headline)
if showsSubtitle {
Text("Subtitle").font(.subheadline).foregroundStyle(.secondary)
}
}
.padding()
// Header auto-adjusts when showsSubtitle toggles
} content: {
contentList
}
.background(Color(.systemBackground))头部高度会通过 + 自动测量 — 无需手动设置尺寸:
GeometryReaderPreferenceKeyswift
StickyBlurHeader {
VStack(alignment: .leading, spacing: 4) {
Text("Title").font(.headline)
if showsSubtitle {
Text("Subtitle").font(.subheadline).foregroundStyle(.secondary)
}
}
.padding()
// 当showsSubtitle切换时,头部会自动调整
} content: {
contentList
}
.background(Color(.systemBackground))Architecture
架构
The component uses a three-layer :
ZStack| Layer | Component | Purpose |
|---|---|---|
| Back | | Content scrolls freely, never clipped |
| Middle | | Progressive blur + adaptive tint |
| Front | Your header view | Floats above blur, transparent background |
The blur engine is VariableBlur by nikstar, which uses the same private API Apple uses internally. It is App Store approved.
该组件使用三层:
ZStack| 层级 | 组件 | 用途 |
|---|---|---|
| 底层 | | 内容自由滚动,永不被裁剪 |
| 中层 | | 渐进式模糊 + 自适应色调 |
| 顶层 | 你的头部视图 | 悬浮在模糊层上方,背景透明 |
模糊引擎采用nikstar的VariableBlur,它使用了苹果内部的私有API,已通过App Store审核。
Critical Rules
重要规则
❌ Never add an opaque background to the header
❌ 切勿给头部添加不透明背景
swift
// WRONG — hides the blur effect
StickyBlurHeader {
Text("Header")
.background(Color.white) // ❌ Breaks the blur
}
// CORRECT — no background on header views
StickyBlurHeader {
Text("Header") // ✅ Transparent, blur shows through
}swift
// 错误 — 会遮挡模糊效果
StickyBlurHeader {
Text("Header")
.background(Color.white) // ❌ 破坏模糊效果
}
// 正确 — 头部视图不设置背景
StickyBlurHeader {
Text("Header") // ✅ 透明,模糊效果可穿透
}❌ Never clip the content hierarchy
❌ 切勿裁剪内容层级
swift
// WRONG
StickyBlurHeader { ... } content: {
VStack { ... }
.clipped() // ❌ Content becomes invisible under header
}
// CORRECT — let content remain visible under blur
StickyBlurHeader { ... } content: {
VStack { ... } // ✅ No clipping
}swift
// 错误
StickyBlurHeader { ... } content: {
VStack { ... }
.clipped() // ❌ 内容在头部下方会消失
}
// 正确 — 让内容在模糊层下方保持可见
StickyBlurHeader { ... } content: {
VStack { ... } // ✅ 不裁剪
}✅ Always set a background on the outer container
✅ 务必给外部容器设置背景
swift
StickyBlurHeader { ... } content: { ... }
.background(Color(.systemBackground)) // ✅ Required for tint to work correctlyswift
StickyBlurHeader { ... } content: { ... }
.background(Color(.systemBackground)) // ✅ 色调正常工作的必要条件Troubleshooting
故障排除
Blur not visible / header looks opaque
模糊效果不可见 / 头部看起来不透明
- Remove any modifier from your header view
.background(...) - Ensure no parent view adds a background before
StickyBlurHeader
- 移除头部视图上的任何修饰符
.background(...) - 确保之前的父视图没有添加背景
StickyBlurHeader
Content disappears under header
内容在头部下方消失
- Remove any from the content or its ancestors
.clipped() - Do not wrap content in a —
ScrollViewprovides its ownStickyBlurHeader
- 移除内容或其祖先视图上的任何修饰符
.clipped() - 不要将内容包裹在中 —
ScrollView已内置滚动视图StickyBlurHeader
Header height is wrong
头部高度不正确
- Do not set explicit heights on the header; let it size naturally
- measures the header automatically
GeometryReader
- 不要给头部设置固定高度;让它自然适应尺寸
- 会自动测量头部
GeometryReader
Tint color looks wrong in dark mode
暗色模式下色调颜色异常
- The tint adapts automatically; ensure is set on the outer view so the adaptive color has a reference
.background(Color(.systemBackground))
- 色调会自动适配;确保外部视图设置了,让自适应颜色有参考基准
.background(Color(.systemBackground))
Build error: "No such module 'ProgressiveBlurHeader'"
构建错误:"No such module 'ProgressiveBlurHeader'"
- Confirm the package is added in Xcode under Package Dependencies
- Clean build folder: Product → Clean Build Folder (⇧⌘K)
- Check the target membership of the package in your app target
- 确认包已添加到Xcode的包依赖中
- 清理构建文件夹:产品 → 清理构建文件夹(⇧⌘K)
- 检查包在你的应用目标中的目标成员身份
iOS version compatibility
iOS版本兼容性
- Minimum deployment target must be iOS 16.0 or higher
- Set in Xcode: Target → General → Minimum Deployments
- 最低部署目标必须为iOS 16.0或更高
- 在Xcode中设置:目标 → 通用 → 最低部署版本
iOS 26 Comparison
iOS 26对比
iOS 26 adds — a native one-liner for sticky blur bars. Use when you need:
.safeAreaBar(edge: .top)ProgressiveBlurHeader- Custom blur radius
- Adjustable tint intensity
- Extended fade below the header
- iOS 16–25 support
iOS 26新增了 — 一行代码即可实现原生粘性模糊栏。当你需要以下功能时,使用:
.safeAreaBar(edge: .top)ProgressiveBlurHeader- 自定义模糊半径
- 可调节的色调强度
- 头部下方的延伸淡入效果
- iOS 16–25版本支持