progressive-blur-header-swiftui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ProgressiveBlurHeader 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/ProgressiveBlurHeader
Select branch:
main
文件 → 添加包依赖 → 粘贴:
https://github.com/dominikmartn/ProgressiveBlurHeader
选择分支:
main

Package.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

StickyBlurHeader

The primary component. Takes two
ViewBuilder
closures:
header
and
content
.
swift
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
}
核心组件。接收两个
ViewBuilder
闭包:
header
content
swift
StickyBlurHeader(
    maxBlurRadius: Double,     // 默认值:5
    fadeExtension: CGFloat,    // 默认值:64
    tintOpacityTop: Double,    // 默认值:0.7
    tintOpacityMiddle: Double  // 默认值:0.5
) {
    // 头部视图(不要设置不透明背景)
} content: {
    // 可滚动内容
}

Parameters

参数说明

ParameterTypeDefaultDescription
maxBlurRadius
Double
5
Max blur at the top edge. 5 = subtle, 10 = moderate, 20 = strong
fadeExtension
CGFloat
64
Points below the header the blur extends
tintOpacityTop
Double
0.7
Tint behind Dynamic Island / status bar
tintOpacityMiddle
Double
0.5
Tint at the header's vertical center
The tint color adapts automatically to light/dark mode.

参数类型默认值描述
maxBlurRadius
Double
5
顶部边缘的最大模糊半径。5=柔和,10=中等,20=强烈
fadeExtension
CGFloat
64
模糊效果延伸到头部下方的点数
tintOpacityTop
Double
0.7
动态岛/状态栏后方的色调透明度
tintOpacityMiddle
Double
0.5
头部垂直中心位置的色调透明度
色调颜色会自动适配亮色/暗色模式。

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
GeometryReader
+
PreferenceKey
— no manual sizing needed:
swift
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))

头部高度会通过
GeometryReader
+
PreferenceKey
自动测量 — 无需手动设置尺寸:
swift
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
:
LayerComponentPurpose
Back
ScrollView
Content scrolls freely, never clipped
Middle
VariableBlurView
+ gradient
Progressive blur + adaptive tint
FrontYour header viewFloats 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
层级组件用途
底层
ScrollView
内容自由滚动,永不被裁剪
中层
VariableBlurView
+ 渐变
渐进式模糊 + 自适应色调
顶层你的头部视图悬浮在模糊层上方,背景透明
模糊引擎采用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 correctly

swift
StickyBlurHeader { ... } content: { ... }
    .background(Color(.systemBackground)) // ✅ 色调正常工作的必要条件

Troubleshooting

故障排除

Blur not visible / header looks opaque

模糊效果不可见 / 头部看起来不透明

  • Remove any
    .background(...)
    modifier from your header view
  • Ensure no parent view adds a background before
    StickyBlurHeader
  • 移除头部视图上的任何
    .background(...)
    修饰符
  • 确保
    StickyBlurHeader
    之前的父视图没有添加背景

Content disappears under header

内容在头部下方消失

  • Remove any
    .clipped()
    from the content or its ancestors
  • Do not wrap content in a
    ScrollView
    StickyBlurHeader
    provides its own
  • 移除内容或其祖先视图上的任何
    .clipped()
    修饰符
  • 不要将内容包裹在
    ScrollView
    中 —
    StickyBlurHeader
    已内置滚动视图

Header height is wrong

头部高度不正确

  • Do not set explicit heights on the header; let it size naturally
  • GeometryReader
    measures the header automatically
  • 不要给头部设置固定高度;让它自然适应尺寸
  • GeometryReader
    会自动测量头部

Tint color looks wrong in dark mode

暗色模式下色调颜色异常

  • The tint adapts automatically; ensure
    .background(Color(.systemBackground))
    is set on the outer view so the adaptive color has a reference
  • 色调会自动适配;确保外部视图设置了
    .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
.safeAreaBar(edge: .top)
— a native one-liner for sticky blur bars. Use
ProgressiveBlurHeader
when you need:
  • Custom blur radius
  • Adjustable tint intensity
  • Extended fade below the header
  • iOS 16–25 support
iOS 26新增了
.safeAreaBar(edge: .top)
— 一行代码即可实现原生粘性模糊栏。当你需要以下功能时,使用
ProgressiveBlurHeader
  • 自定义模糊半径
  • 可调节的色调强度
  • 头部下方的延伸淡入效果
  • iOS 16–25版本支持