AgentStack
SKILL verified MIT Self-run

Swiftui Graphics

skill-athevon-genjutsu-swiftui-graphics · by AThevon

Advanced SwiftUI visuals - Metal shaders (.colorEffect, .layerEffect, .distortionEffect), .visualEffect, Liquid Glass (iOS 26), Canvas, holographic and CRT effects.

No reviews yet
0 installs
11 views
0.0% view→install

Install

$ agentstack add skill-athevon-genjutsu-swiftui-graphics

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Swiftui Graphics? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

SwiftUI Graphics

> Advanced SwiftUI visuals: Metal shaders, visual effects, Liquid Glass, Canvas. > Loaded for advanced thesis (shaders, holographic, liquid-glass, distortion). > Foundation: ../swiftui-motion/SKILL.md covers the basics. > Concise rules here. Deep-dives in references/.


Decision Tree: Which API?

| Need | API | |---|---| | Pixel-level color manipulation | .colorEffect(ShaderLibrary....) | | Pixel position / distortion | .distortionEffect(ShaderLibrary....) | | Full layer with overlay (mix shader + bg) | .layerEffect(ShaderLibrary....) | | View modifier with geometry context | .visualEffect { content, geometry in } | | Custom drawing (paths, gradients) | Canvas { context, size in } | | iOS 26+ glassmorphism | .glassEffect() / GlassEffectContainer | | Performance dump | Canvas with .opaque(true) then export |

> Default order of escalation: built-in modifiers -> .visualEffect -> Canvas -> Metal shader. Reach for shaders only when the effect is per-pixel and animated.


Metal Shaders Intro

SwiftUI binds to Metal Shading Language (MSL) via three modifiers shipped in iOS 17: .colorEffect, .distortionEffect, .layerEffect. You author a .metal file in your app target, mark functions with the [[ stitchable ]] attribute, and SwiftUI auto-generates the Swift binding via ShaderLibrary.(...). One library per app target. Shaders run on the GPU at native resolution; arguments are passed as .float, .float2, .color, .image from Swift. iOS 17+ only; for older targets, fall back to gradients, blur, or Canvas.

The three slots differ by what data they receive:

  • .colorEffect: gets (position, color), returns transformed color. No neighbor sampling.
  • .distortionEffect: gets (position), returns a new sample position. Pixels move, colors do not change.
  • .layerEffect: gets (position, SwiftUI::Layer layer), returns final color. Can sample anywhere within maxSampleOffset. Most expensive.

Recipe: Ripple .layerEffect

Touch ripple that displaces nearby pixels along a sine wave.

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 
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);
}

Why this works:

  • [[ stitchable ]] exposes the function to SwiftUI's runtime.
  • The first two args (position, SwiftUI::Layer layer) are injected by SwiftUI for any .layerEffect. Your Swift-side args start at index 2.
  • maxSampleOffset tells SwiftUI how far you may sample beyond the view bounds. Underestimate and you get clipping. Overestimate and you waste GPU.

Recipe: Holographic .colorEffect

Oil-slick rainbow shimmer driven by time or scroll offset. Preserves luminance so dark regions stay dark.

// Holographic.metal
#include 
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))
                )
        }
    }
}

> The 2.094 and 4.188 offsets are 2pi/3 and 4pi/3 -- they spread the three sine waves to RGB phases. Keep them.


Recipe: CRT Scanlines

Vintage CRT effect: scanlines, flicker, subtle chromatic aberration.

// CRT.metal
#include 
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)))

> For real chromatic aberration (shifted R/B sample positions), promote to .layerEffect. The version above only tints, which reads as CRT at small scale.


.visualEffect (iOS 17+)

Modifier that exposes the view's GeometryProxy so you can react to its frame in any coordinate space without GeometryReader boilerplate.

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
}

Use cases:

  • Parallax cards (move slower than scroll)
  • Scroll-driven scale (cards grow as they enter view)
  • Sticky reveal (clamp position via .offset(y: max(0, -y)))

> .visualEffect is purely visual: the geometry it returns is read-only and the modifier cannot trigger state updates. Don't try to write to @State from inside.


Liquid Glass (iOS 26)

System glassmorphism with adaptive depth and morphing transitions. Built into the OS, optimized at the system level.

@Namespace var glassNS

struct HeroCard: View {
    var body: some View {
        if #available(iOS 26.0, *) {
            Image("hero")
                .resizable()
                .scaledToFit()
                .glassEffect(.regular)
                .glassEffectID("hero", in: glassNS)
        } else {
            Image("hero")
                .resizable()
                .scaledToFit()
                .background(.ultraThinMaterial)
        }
    }
}

For grouped surfaces that should morph as one (e.g., a tab bar that splits into separate pills on hover), wrap them in a container:

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

> Pre-iOS 26: use .background(.ultraThinMaterial) for static glass. For morphing transitions, fall back to matchedGeometryEffect on a material-backed view (see swiftui-motion).

Deep-dive: references/liquid-glass-deep.md.


Canvas (SwiftUI Native Drawing)

Vector drawing API. Paths, gradients, text, blend modes -- without leaving SwiftUI.

struct SparkleField: View {
    var body: some View {
        Canvas { context, size in
            for _ in 0.. `TimelineView(.animation)` redraws at the screen refresh rate. Use `.animation(minimumInterval: 0.1)` for slower animations to save power.

Deep-dive: `references/canvas-swiftui.md`.

---

## Performance Considerations

| Effect | Cost | Notes |
|---|---|---|
| `.colorEffect` | low | Runs per pixel, simple math, no neighbor access |
| `.distortionEffect` | medium | Sampling cost, branch-free is critical |
| `.layerEffect` | high | Full layer access, can sample anywhere |
| `Canvas` with TimelineView | varies | Depends on draw count and complexity |
| `.glassEffect` | medium-high | GPU heavy, fine on modern devices |

**Rules:**
1. Avoid stacking >1 `.layerEffect` on the same view -- each is a full render pass.
2. Precompute static parts (gradients, paths) outside the animated subtree.
3. Profile with Instruments **GPU Frame Capture** and **Metal System Trace** before optimizing blindly.

---

## Anti-Patterns

### 1. Stacked `.colorEffect` modifiers

Each modifier is a separate render pass.

```swift
// 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 redrawn on every state change

Canvas re-runs when any ancestor state changes. Without TimelineView or EquatableView, you redraw on input you never intended.

// 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. Hardcoded color values inside shader

Forces a recompile to change color. Pass them through.

// 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 everywhere

Liquid Glass is expensive and visually noisy when overused. Reserve for hero / chrome surfaces.

// 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)
}

Quick Reference

| Need | Load | |---|---| | Metal recipes deep dive | references/metal-recipes.md | | Liquid Glass patterns + iOS 26 specifics | references/liquid-glass-deep.md | | Canvas drawing patterns | references/canvas-swiftui.md | | Base animations | ../swiftui-motion/SKILL.md | | Foundation | ../motion-principles/SKILL.md | | Mobile UX (iOS) | ../mobile-principles/SKILL.md | | Desktop UX (macOS) | ../desktop-principles/SKILL.md |


Sources

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.