Advanced SwiftUI visuals - Metal shaders (.colorEffect, .layerEffect, .distortionEffect), .visualEffect, Liquid Glass (iOS 26), Canvas, holographic and CRT effects.
npx skills add https://github.com/AThevon/genjutsu --skill 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/.
| 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.
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.<functionName>(...). 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..layerEffectTouch 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 <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);
}
Why this works:
[[ stitchable ]] exposes the function to SwiftUI's runtime.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..colorEffectOil-slick rainbow shimmer driven by time or scroll offset. Preserves luminance so dark regions stay dark.
// 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))
)
}
}
}
> The 2.094 and 4.188 offsets are 2pi/3 and 4pi/3 -- they spread the three sine waves to RGB phases. Keep them.
Vintage CRT effect: scanlines, flicker, subtle chromatic aberration.
// 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)))
> 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:
.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.
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.
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..<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)))
)
}
}
}
}
To animate, wrap in TimelineView(.animation) so the closure re-runs at frame rate:
TimelineView(.animation) { timeline in
Canvas { context, size in
let t = timeline.date.timeIntervalSinceReferenceDate
// draw using t as time
}
}
> 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.
| 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:
.layerEffect on the same view -- each is a full render pass..colorEffect modifiersEach modifier is a separate render pass.
// 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)))
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) }
}
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)))
.glassEffect everywhereLiquid 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)
}
| 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 |
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Improves the quality of images, especially screenshots, by enhancing resolution, sharpness, and clarity. Perfect for preparing images for presentations, documentation, or social media posts.
Downloads videos from YouTube and other platforms for offline viewing, editing, or archival. Handles various formats and quality options.
Lightweight WSI tile extraction and preprocessing. Use for basic slide processing tissue detection, tile extraction, stain normalization for H&E images. Best for simple pipelines, dataset preparation, quick tile-based analysis. For advanced spatial proteomics, multiplexed imaging, or deep learning pipelines use pathml.
Microscopy data management platform. Access images via Python, retrieve datasets, analyze pixels, manage ROIs/annotations, batch processing, for high-content screening and microscopy workflows.
Python library for working with DICOM (Digital Imaging and Communications in Medicine) files. Use this skill when reading, writing, or modifying medical imaging data in DICOM format, extracting pixel data from medical images (CT, MRI, X-ray, ultrasound), anonymizing DICOM files, working with DICOM metadata and tags, converting DICOM images to other formats, handling compressed DICOM data, or processing medical imaging datasets. Applies to tasks involving medical image analysis, PACS systems, radiology workflows, and healthcare imaging applications.
This skill should be used when working with pre-trained transformer models for natural language processing, computer vision, audio, or multimodal tasks. Use for text generation, classification, question answering, translation, summarization, image classification, object detection, speech recognition, and fine-tuning models on custom datasets.
Take athevon/swiftui-graphics from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.