mcpField
S
swiftui-graphics

swiftui-graphics

github.com/athevon设计与 UI698937 次安装3 周前更新
代码仓库

关于 swiftui-graphics

面向 Claude Code、claude.ai 和 Cowork 的 SwiftUI 高级视觉技能,涵盖 Metal 着色器、Canvas、视觉效果与 Liquid Glass,帮助开发者选择渲染 API,并说明性能和回退方案。

主要功能

  1. 01Metal 着色器效果
  2. 02Canvas 绘制与动画
  3. 03结合几何信息的视觉效果
  4. 04Liquid Glass 模式
  5. 05渲染性能指导

适用场景

  1. 01创建像素级颜色或扭曲效果
  2. 02绘制动态矢量视觉
  3. 03应用响应滚动的卡片效果
  4. 04为支持的界面添加玻璃质感表面

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/athevon/genjutsu --skill swiftui-graphics

安装后在 Claude Code 中输入/swiftui-graphics确认已加载。

详细介绍

版本敏感。 以下每个 API 名称、SDK 门槛和浏览器支持声明均已于 2026-09-08 根据一手资料核实。核实了哪些内容以及核实时间,详见 _jutsu/VERSIONS.md。如果该日期已久远,请在依据版本号采取行动前重新核实。

SwiftUI 图形

高级 SwiftUI 视觉效果:Metal 着色器、视觉效果、Liquid Glass、Canvas。 用于高级主题(着色器、全息效果、液态玻璃、扭曲)。 基础内容:../swiftui-motion/SKILL.md 涵盖基础知识。 此处提供简明规则。深入内容见 references/。


决策树:该用哪个 API?

需求API
像素级颜色处理.colorEffect(ShaderLibrary....)
像素位置/扭曲.distortionEffect(ShaderLibrary....)
带叠加层的完整图层(混合着色器 + 背景).layerEffect(ShaderLibrary....)
带几何上下文的视图修饰符.visualEffect { content, geometry in }
自定义绘制(路径、渐变)Canvas { context, size in }
iOS 26+ 毛玻璃效果.glassEffect() / GlassEffectContainer
性能导出使用 .opaque(true) 的 Canvas,然后导出

**默认的升级顺序:**内置修饰符 -> .visualEffect -> Canvas -> Metal 着色器。仅当效果需要逐像素处理且带动画时,才考虑使用着色器。


Metal 着色器简介

SwiftUI 通过 iOS 17 随附的三个修饰符与 Metal 着色语言(MSL)绑定:.colorEffect、.distortionEffect、.layerEffect。你可以在应用目标中编写 .metal 文件,为函数添加 [[ stitchable ]] 属性,SwiftUI 会通过 ShaderLibrary.<functionName>(...) 自动生成 Swift 绑定。每个应用目标对应一个库。着色器在 GPU 上以原生分辨率运行;参数从 Swift 侧以 .float、.float2、.color、.image 传入。可用性:iOS 17.0+、iPadOS 17.0+、Mac Catalyst 17.0+、macOS 14.0+、tvOS 17.0+、visionOS 1.0+,不支持 watchOS。低于这些版本时,请改用渐变、模糊或 Canvas。

三个插槽接收的数据各不相同:

  • .colorEffect:接收 (position, color),返回变换后的颜色。不能对相邻像素采样。
  • .distortionEffect:接收 (position),返回新的采样位置。像素会移动,但颜色不会改变。
  • .layerEffect:接收 (position, SwiftUI::Layer layer),返回最终颜色。可以在 maxSampleOffset 范围内任意采样。开销最大。

示例:波纹 .layerEffect

触摸波纹,沿正弦波置换附近的像素。

struct RippleView: View {
    @State var rippleOrigin: CGPoint = .zero
    @State var rippleTime: Float = 0

    var body: some View {
        Image("photo")
            .resizable()
            .scaledToFit()
            .layerEffect(
                ShaderLibrary.ripple(
                    .float2(Float(rippleOrigin.x), Float(rippleOrigin.y)),
                    .float(rippleTime),
                    .float(0.05) // amplitude
                ),
                maxSampleOffset: CGSize(width: 50, height: 50)
            )
            .onTapGesture { location in
                rippleOrigin = location
                rippleTime = 0
                withAnimation(.linear(duration: 1.2)) {
                    rippleTime = 1.2
                }
            }
    }
}
// Ripple.metal
#include <SwiftUI/SwiftUI_Metal.h>
using namespace metal;

[[ stitchable ]]
half4 ripple(float2 position, SwiftUI::Layer layer,
             float2 origin, float time, float amp) {
    float distance = length(position - origin);
    float wave = sin(distance * 0.05 - time * 8.0) * amp;
    float2 dir = normalize(position - origin);
    float falloff = 1.0 / max(distance, 1.0);
    float2 displaced = position + dir * wave * falloff * 50.0;
    return layer.sample(displaced);
}

原理:

  • [[ stitchable ]] 使 SwiftUI 的运行时可以访问该函数。
  • 前两个参数(position、SwiftUI::Layer layer)由 SwiftUI 为任何 .layerEffect 注入。Swift 侧参数从索引 2 开始。
  • maxSampleOffset 告知 SwiftUI 你可能会在视图边界之外采样多远。估算过小会导致裁切,估算过大则会浪费 GPU 资源。

示例:全息 .colorEffect

由时间或滚动偏移驱动的油膜彩虹闪烁效果。保留亮度,因此暗部仍保持暗色。

// Holographic.metal
#include <SwiftUI/SwiftUI_Metal.h>
using namespace metal;

[[ stitchable ]]
half4 holographic(float2 position, half4 color, float time) {
    float n = position.x * 0.01 + position.y * 0.005 + time * 0.3;
    half3 rainbow = half3(
        sin(n * 2.0) * 0.5 + 0.5,
        sin(n * 2.0 + 2.094) * 0.5 + 0.5,
        sin(n * 2.0 + 4.188) * 0.5 + 0.5
    );
    half luminance = dot(color.rgb, half3(0.299, 0.587, 0.114));
    return half4(mix(color.rgb, rainbow * luminance * 2.0, 0.5), color.a);
}
struct HolographicCard: View {
    let startTime = Date()

    var body: some View {
        TimelineView(.animation) { timeline in
            let elapsed = Float(timeline.date.timeIntervalSince(startTime))
            Image("card")
                .resizable()
                .scaledToFit()
                .colorEffect(
                    ShaderLibrary.holographic(.float(elapsed))
                )
        }
    }
}

2.094 和 4.188 的偏移分别是 2pi/3 和 4pi/3——它们会将三个正弦波分散到 RGB 相位中。请保留这些值。


示例:CRT 扫描线

复古 CRT 效果:扫描线、闪烁和轻微色差。

// CRT.metal
#include <SwiftUI/SwiftUI_Metal.h>
using namespace metal;

[[ stitchable ]]
half4 crt(float2 position, half4 color, float time) {
    float scanline = sin(position.y * 1.5) * 0.04;
    float flicker = sin(time * 60.0) * 0.02;
    half3 result = color.rgb * (1.0 - scanline - flicker);
    // chromatic aberration on R/B channels
    return half4(result.r * 1.05, result.g, result.b * 1.05, color.a);
}
.colorEffect(ShaderLibrary.crt(.float(elapsed)))

如需真实的色差(偏移 R/B 采样位置),请升级为 .layerEffect。上面的版本仅添加色调,在小尺寸下看起来像 CRT。


.visualEffect(iOS 17+)

此修饰符会公开视图的 GeometryProxy,让你无需编写 GeometryReader 样板代码,即可根据视图在任意坐标空间中的框架作出响应。

ScrollView {
    LazyVStack(spacing: 16) {
        ForEach(items) { item in
            CardView(item: item)
                .visualEffect { content, proxy in
                    let y = proxy.frame(in: .scrollView).minY
                    let scale = scale(for: y)
                    let opacity = opacity(for: y)
                    return content
                        .scaleEffect(scale)
                        .opacity(opacity)
                }
        }
    }
}

func scale(for y: CGFloat) -> CGFloat {
    let progress = max(0, min(1, y / 600))
    return 0.85 + progress * 0.15
}

使用场景:

  • 视差卡片(移动速度慢于滚动)
  • 滚动驱动的缩放(卡片进入视图时变大)
  • 粘性显现(通过 .offset(y: max(0, -y)) 限制位置)

.visualEffect 纯粹用于视觉效果:它返回的几何信息是只读的,且此修饰符无法触发状态更新。请勿尝试在其中写入 @State。


Liquid Glass(iOS 26)

系统级玻璃拟态,具有自适应深度和形变过渡效果。内置于操作系统中,并在系统层面进行了优化。

struct HeroCard: View {
    // @Namespace is a property wrapper - it has to live inside the View, not at file scope.
    @Namespace private var glassNS

    var body: some View {
        if #available(iOS 26.0, macOS 26.0, *) {
            // glassEffectID only does anything inside a GlassEffectContainer.
            GlassEffectContainer(spacing: 20.0) {
                Label("Hero", systemImage: "sparkles")
                    .font(.title2)
                    .padding()
                    // Default shape is a Capsule (DefaultGlassEffectShape); pass `in:` for anything else.
                    .glassEffect(.regular, in: .rect(cornerRadius: 24))
                    .glassEffectID("hero", in: glassNS)
            }
        } else {
            Label("Hero", systemImage: "sparkles")
                .font(.title2)
                .padding()
                .background(.ultraThinMaterial, in: .rect(cornerRadius: 24))
        }
    }
}

对于应作为一个整体形变的分组表面(例如,悬停时拆分为多个独立胶囊形状的标签栏),请将它们包装在容器中:

GlassEffectContainer(spacing: 12) {
    ForEach(tabs) { tab in
        TabIcon(tab: tab)
            .glassEffect(.regular)
            .glassEffectID(tab.id, in: glassNS)
    }
}

iOS 26 之前:静态玻璃效果请使用 .background(.ultraThinMaterial)。对于形变过渡,请改用应用于材质背景视图的 matchedGeometryEffect(参见 swiftui-motion)。

深入了解:references/liquid-glass-deep.md。


Canvas(SwiftUI 原生绘图)

矢量绘图 API。路径、渐变、文本、混合模式——无需离开 SwiftUI。

struct SparkleField: View {
    var body: some View {
        Canvas { context, size in
            for _ in 0..<20 {
                let x = Double.random(in: 0...size.width)
                let y = Double.random(in: 0...size.height)
                let radius = Double.random(in: 1...4)
                context.fill(
                    Path(ellipseIn: CGRect(x: x, y: y, width: radius, height: radius)),
                    with: .color(.white.opacity(.random(in: 0.3...1.0)))
                )
            }
        }
    }
}

要添加动画,请将其包装在 TimelineView(.animation) 中,使闭包以帧率重新运行:

TimelineView(.animation) { timeline in
    Canvas { context, size in
        let t = timeline.date.timeIntervalSinceReferenceDate
        // draw using t as time
    }
}

TimelineView(.animation) 会以屏幕刷新率重绘。对于较慢的动画,请使用 .animation(minimumInterval: 0.1) 以节省电量。

深入了解:references/canvas-swiftui.md。


性能注意事项

效果开销说明
.colorEffect低逐像素运行,进行简单计算,不访问相邻像素
.distortionEffect中有采样开销;避免分支至关重要
.layerEffect高可访问整个图层,并在任意位置采样
带有 TimelineView 的 Canvas不定取决于绘制次数和复杂度
.glassEffect中高GPU 开销大,在现代设备上表现良好

规则:

  1. 避免在同一个视图上叠加超过 1 个 .layerEffect——每个都会触发一次完整的渲染通道。
  2. 将静态部分(渐变、路径)预先计算,并放在动画子树之外。
  3. 不要盲目优化;请先使用 Instruments 中的 GPU Frame Capture 和 Metal System Trace 进行性能分析。

反模式

1. 叠加 .colorEffect 修饰符

每个修饰符都会触发单独的渲染通道。

// BAD -- 5 GPU passes
Image(...)
    .colorEffect(ShaderLibrary.tint(...))
    .colorEffect(ShaderLibrary.scanlines(...))
    .colorEffect(ShaderLibrary.grain(...))
    .colorEffect(ShaderLibrary.vignette(...))
    .colorEffect(ShaderLibrary.chromatic(...))

// GOOD -- one shader does all the ops in one pass
Image(...)
    .colorEffect(ShaderLibrary.crtCombo(.float(time)))
2. 每次状态变化都重绘 Canvas

任何祖先视图的状态变化都会导致 Canvas 重新运行。若没有 TimelineView 或 EquatableView,就会在不必要的输入变化时重绘。

// BAD -- redraws on parent re-render
struct Parent: View {
    @State var unrelated = 0
    var body: some View {
        VStack {
            Button("tick") { unrelated += 1 }
            Canvas { context, size in expensiveDraw(context, size) }
        }
    }
}

// GOOD -- isolate via EquatableView or TimelineView
TimelineView(.animation) { _ in
    Canvas { context, size in expensiveDraw(context, size) }
}
3. 在着色器中硬编码颜色值

更改颜色时必须重新编译。请改为传入颜色。

// BAD
half3 tint = half3(1.0, 0.4, 0.2);

// GOOD -- accept color from Swift
[[ stitchable ]]
half4 tinted(float2 pos, half4 color, half4 tint) {
    return half4(color.rgb * tint.rgb, color.a);
}
.colorEffect(ShaderLibrary.tinted(.color(themeAccent)))
4. 到处使用 .glassEffect

过度使用 Liquid Glass 会带来高开销和视觉杂乱。请仅用于主视觉或界面装饰表面。

// BAD -- 30 glass cards in a list
LazyVStack {
    ForEach(items) { item in
        Card(item: item).glassEffect(.regular) // GPU melt
    }
}

// GOOD -- glass on the floating tab bar, opaque cards underneath
ZStack(alignment: .bottom) {
    ScrollView { LazyVStack { ForEach(items) { Card(item: $0) } } }
    TabBar().glassEffect(.regular)
}

快速参考

需求加载
深入了解 Metal 配方references/metal-recipes.md
Liquid Glass 模式和 iOS 26 特性references/liquid-glass-deep.md
Canvas 绘图模式references/canvas-swiftui.md
基础动画../swiftui-motion/SKILL.md
基础知识../motion-principles/SKILL.md
移动端 UX(iOS)../mobile-principles/SKILL.md
桌面端 UX(macOS)../desktop-principles/SKILL.md

来源

评论

支持 Markdown