swiftui-graphics について
Claude Code、claude.ai、Cowork向けのSwiftUI高度ビジュアルスキルです。Metalシェーダー、Canvas、ビジュアルエフェクト、Liquid Glassを扱い、描画APIの選択や性能、代替手段の指針を提供します。
主な機能
- 01Metalシェーダーエフェクト
- 02Canvasによる描画とアニメーション
- 03ジオメトリに応じたビジュアルエフェクト
- 04Liquid Glassの実装パターン
- 05描画性能の指針
活用シーン
- 01ピクセル単位の色・歪みエフェクトの作成
- 02アニメーションするベクター描画
- 03スクロールに反応するカード演出
- 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 Graphics
Metal シェーダー、ビジュアルエフェクト、Liquid Glass、Canvas など、高度な SwiftUI ビジュアルについて説明します。 高度なテーマ(シェーダー、ホログラフィック、リキッドグラス、歪み)で読み込まれます。 基礎については
../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 の後にエクスポート |
段階的に使う API のデフォルト順序: 組み込みモディファイア ->
.visualEffect->Canvas-> Metal シェーダー。ピクセル単位でアニメーションするエフェクトに限り、シェーダーを使ってください。
Metal シェーダーの概要
SwiftUI は iOS 17 に搭載された 3 つのモディファイア .colorEffect、.distortionEffect、.layerEffect を介して、Metal Shading Language(MSL)にバインドします。アプリのターゲットに .metal ファイルを追加し、関数に [[ stitchable ]] 属性を付けると、SwiftUI が ShaderLibrary.<functionName>(...) を使って Swift バインディングを自動生成します。アプリのターゲットごとにライブラリは 1 つです。シェーダーはネイティブ解像度で 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 にフォールバックしてください。
3 つのスロットは、受け取るデータがそれぞれ異なります。
.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 のランタイムからこの関数を利用できるようになります。- 最初の 2 つの引数(
position、SwiftUI::Layer layer)は、すべての.layerEffectに対して SwiftUI が挿入します。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 です。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に書き込もうとしないでください。
リキッドグラス(iOS 26)
適応型の奥行きとモーフィングするトランジションを備えた、システム標準のグラスモーフィズムです。OS に組み込まれており、システムレベルで最適化されています。
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 | 高 | レイヤー全体にアクセスでき、任意の場所をサンプリングできます |
Canvas と TimelineView | 変動 | 描画回数と複雑さによって異なります |
.glassEffect | 中〜高 | GPU 負荷が高いですが、最新のデバイスでは問題ありません |
ルール:
- 同じビューに
.layerEffectを 1 つより多く重ねないでください。それぞれが完全なレンダーパスになります。 - 静的な部分(グラデーションやパス)は、アニメーション対象のサブツリーの外で事前計算します。
- むやみに最適化する前に、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 をあらゆる場所で使う
リキッドグラスはコストが高く、使いすぎると視覚的なノイズになります。ヒーローやクロームのサーフェスに限定してください。
// 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 |
| リキッドグラスのパターンと 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 |