ハイライト
iOS 18 の SwiftUI では MeshGradient、TextRenderer、カスタム Transition プロトコル、Metal シェーダー統合が追加され、ビジュアルエフェクトは「組み込み modifier だけ」から「ピクセル単位のカスタマイズ」へと進化しました。
主要内容
SwiftUI アプリを作るとき、ビジュアルエフェクトはよく 2 つの壁にぶつかります。組み込み modifier では足りない場合——例えば scrollTransition は回転とスケールしかできず、色相シフトには GeometryReader を自前で書く必要がある。あるいは文字を 1 グリフずつアニメーションしたいが、Text ビューは分割できないブラックボックスで、個々のグリフ位置にアクセスできない、といったケースです。
Apple はこれらのギャップを埋める 5 つの新ツールを提供しました。scrollTransition と visualEffect の 2 つの modifier は、スクロールシーンでの位置ベースとジオメトリベースの 2 種類の視覚調整をカバーします。MeshGradient は色付きコントロールポイントのグリッド上で補間を行い、線形/放射状グラデーションよりはるかに柔軟で、コントロールポイント位置もアニメーション可能です。カスタム Transition プロトコルでは TransitionPhase で willAppear と didDisappear を区別し、方向性のあるトランジションを書けます。TextRenderer プロトコルは Text.Layout の lines、runs、runSlices をすべて公開し、TextAttribute で重要な文字をマークすれば、spring アニメーションで 1 グリフずつアニメーションできます。最後に、Metal シェーダーは ShaderLibrary と layerEffect 経由で SwiftUI ビューツリーに直接統合され、GPU でピクセル単位に計算するため、CPU 方式を大きく上回るパフォーマンスを発揮します。
セッション全体の考え方は、ビジュアルエフェクトは反復的な調整が必要なので、すべての API を宣言的・合成可能・リアルタイムプレビュー可能に設計し、「アイデア」から「効果を見る」までの遅延を減らすことです。
詳細
カスタムスクロールエフェクト
scrollTransition は phase パラメータを公開し、値域は leading / center / trailing。phase.value(浮動小数オフセット)と phase.isIdentity(中心にあるか)の 2 つのプロパティも提供します。視差効果では、クリッピングコンテナではなく画像コンテンツだけをオフセットすると、「窓越しの透視」感が得られます(03:14)。
visualEffect は GeometryProxy ベースでビューのグローバル位置とサイズを提供し、色相回転、ぼかし、スケールなど座標ベースの視覚変化が可能です(04:41):
RoundedRectangle(cornerRadius: 24)
.fill(.purple)
.visualEffect({ content, proxy in
content
.hueRotation(Angle(degrees: proxy.frame(in: .global).origin.y / 10))
})
キーポイント:
visualEffectのクロージャ引数proxyはGeometryProxyで、frame、safeAreaInsets などのジオメトリを読み取れる- 戻り値は
some Viewで、視覚系 modifier(opacity、blur、hueRotation など)のみ使用可能。レイアウトは変更できない - layout pass をトリガーしないため、GeometryReader + 状態駆動よりパフォーマンスが良い
Mesh Gradient
MeshGradient は width × height でグリッドを定義し、各コントロールポイントに SIMD2 座標と色を指定すると、SwiftUI が点間を自動補間します(07:30):
MeshGradient(
width: 3,
height: 3,
points: [
[0.0, 0.0], [0.5, 0.0], [1.0, 0.0],
[0.0, 0.5], [0.9, 0.3], [1.0, 0.5],
[0.0, 1.0], [0.5, 1.0], [1.0, 1.0]
],
colors: [
.black,.black,.black,
.blue, .blue, .blue,
.green, .green, .green
]
)
キーポイント:
points配列の長さはwidth * heightと一致必須。各要素は[Float, Float]で 0〜1- 中心点
[0.9, 0.3]が正中からずれ、青い領域が右上にシフト——これが Mesh Gradient の核心:コントロールポイント位置が色分布を決める - コントロールポイント位置を
@Stateにバインドし、withAnimationで色の流れを実現可能
カスタム Transition
iOS 18 で Transition プロトコルが追加され、TransitionPhase は willAppear、identity、didDisappear の 3 状態を区別します(10:36):
struct Twirl: Transition {
func body(content: Content, phase: TransitionPhase) -> some View {
content
.scaleEffect(phase.isIdentity ? 1 : 0.5)
.opacity(phase.isIdentity ? 1 : 0)
.blur(radius: phase.isIdentity ? 0 : 10)
.rotationEffect(
.degrees(
phase == .willAppear ? 360 :
phase == .didDisappear ? -360 : .zero
)
)
.brightness(phase == .willAppear ? 1 : 0)
}
}
キーポイント:
phase.isIdentityが true のときビューは完全表示。それ以外はphase.valueで連続補間可能phase == .willAppearとphase == .didDisappearで回転方向を区別——入場時は正転、退場時は反転brightnessは入場時のみ 1 を加え、登場瞬間にハイライトを付けて注意を引く
TextRenderer
TextRenderer プロトコルの核心メソッドは draw(layout:in:) です。Text.Layout は lines → runs → runSlices の階層を提供し、最小の runSlice が 1 グリフに対応します(13:29)。アニメーションを駆動するには、renderer が Animatable に準拠し、animatableData が elapsedTime を指します。TextAttribute と組み合わせて特定の文字範囲をマークすれば、マーク部分だけ 1 グリフずつアニメーションし、残りは素早くフェードインできます(14:01)。
Metal シェーダー統合
Metal シェーダー関数は [[ stitchable ]] でマークし、SwiftUI は ShaderLibrary.Ripple(...) で名前指定呼び出しします。Layer Effect は各ピクセルの position と元のレイヤーコンテンツを取得し、歪み、色差など GPU レベルのエフェクトが可能です(22:55):
[[ stitchable ]]
half4 Ripple(
float2 position,
SwiftUI::Layer layer,
float2 origin,
float time,
float amplitude,
float frequency,
float decay,
float speed
) {
float distance = length(position - origin);
float delay = distance / speed;
time -= delay;
time = max(0.0, time);
float rippleAmount = amplitude * sin(frequency * time) * exp(-decay * time);
float2 n = normalize(position - origin);
float2 newPosition = position + rippleAmount * n;
half4 color = layer.sample(newPosition);
color.rgb += 0.3 * (rippleAmount / amplitude) * color.a;
return color;
}
キーポイント:
positionは現在のピクセル座標、layerはビューのレイヤーコンテンツ。layer.sample(at:)でサンプリング- 波紋は
originから外側へ伝播。distance / speedで遅延を計算——遠いピクセルほど遅く振動開始 sin(frequency * time) * exp(-decay * time)は減衰正弦波——時間経過で振幅が小さくなるcolor.rgb += 0.3 * ...で波の頂点を明るくし、光の屈折を模倣
SwiftUI 側では RippleEffect ViewModifier が keyframeAnimator をラップし、elapsedTime を 0 から duration までアニメーション。各フレームを RippleModifier に渡し、後者が visualEffect + layerEffect でシェーダーを呼び出します(23:36)。
重要ポイント
-
何をするか:
visualEffectでスクロール位置ベースの色相/ぼかしグラデーションリストを作る。なぜ価値があるか: 単色リストは単調。proxy.frame(in: .global).origin.yで hueRotation を駆動すれば、レイアウトコード 0 行で色彩の层次感が得られる。始め方:LazyVStackの各 item に.visualEffect { content, proxy in content.hueRotation(...) }を追加し、y 座標の除数を調整して自然なグラデーションにする。 -
何をするか: ブランドページの静的背景色を MeshGradient に置き換える。なぜ価値があるか: MeshGradient のコントロールポイントはアニメーション可能で、CAGradientLayer より柔軟。純粋に宣言的で Core Animation コード不要。始め方: 3×3 の MeshGradient を定義し、中心点座標を
@Stateにバインド。withAnimation(.easeInOut(duration: 2))で座標値を切り替え、色の流れを確認する。 -
何をするか: TextRenderer + TextAttribute で重要情報の 1 グリフずつ入場アニメーション。なぜ価値があるか: 通常の opacity トランジションは全文字が同時に現れ、重点がない。1 グリフずつの spring アニメーションでタイトルのキーワードを強調し、視線を引ける。始め方:
EmphasisAttribute: TextAttributeを定義し、Text で.customAttribute(EmphasisAttribute())でキーワードをマーク。renderer で run の attribute の有無を確認し、1 グリフアニメーションか素早いフェードインかを決める。 -
何をするか: 画像インタラクションに Metal シェーダーの波紋エフェクトを追加。なぜ価値があるか: スケールだけのフィードバックには方向性がない。タッチ点から広がる波紋で「ここをタップした」と明確に伝わる。始め方: .metal ファイルに
[[ stitchable ]]の Ripple 関数を書き、SwiftUI 側でkeyframeAnimatorが elapsedTime を駆動。layerEffectでシェーダーを適用する。
関連セッション
- What’s new in SwiftUI — iOS 18 SwiftUI の全体更新概観。MeshGradient など新 API の初登場を含む
- Enhance your UI animations and transitions — ナビゲーションとプレゼンテーションでの zoom transition を深掘り。本セッションのカスタム Transition と補完関係
- Create custom hover effects in visionOS — visionOS の hover effect カスタマイズ。SwiftUI ビジュアルエフェクト体系に基づく
- SwiftUI essentials — SwiftUI 基礎概念の復習。本セッションの高度なエフェクトの前に学ぶのに適する
- What’s new in SF Symbols 6 — SF Symbols の新アニメーション能力。TextRenderer と組み合わせてアイコン+文字連動エフェクトが可能
コメント
GitHub Issues · utterances