Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
SwiftUI と Shader を活用した楽しいオンボーディング起動画面の作成
Search
Megabits_mzq
September 03, 2025
Programming
140
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
SwiftUI と Shader を活用した楽しいオンボーディング起動画面の作成
Megabits_mzq
September 03, 2025
More Decks by Megabits_mzq
See All by Megabits_mzq
OTP を自動で入力する裏技
megabitsenmzq
0
180
Liquid Glass, どこが変わったのか
megabitsenmzq
0
180
iPhone 16 Camera Control
megabitsenmzq
0
160
240fps で画像処理したい
megabitsenmzq
0
250
Swift 開発が楽になる道具たち
megabitsenmzq
1
790
Animoji を作ってみた
megabitsenmzq
0
210
MainMenu.xib を翻訳してみた
megabitsenmzq
0
310
WKWebView とめんどくさいお友達
megabitsenmzq
1
790
先週解決した SwiftUI 問題
megabitsenmzq
0
160
Other Decks in Programming
See All in Programming
Detecting Compromised CI with eBPF and Cilium Tetragon
lizrice
0
180
複数の Claude Code が"放置"されてしまう問題をCLI ダッシュボードを自作して解決した話
sumihiro3
1
680
わからない話を追いかけたら、プログラミング言語を作る側にいた
ydah
3
500
仕様駆動開発の消費期限
watany
20
8.2k
仕様書を書く前にハーネスを作る - Agent Native開発は「探索を速く、判定を固く」
gotalab555
4
1.6k
AWS CDK を「作」ってみた 〜フルスクラッチで見えた CDK の裏側〜 / aws-cdk-from-scratch
gotok365
3
2.8k
AI時代のPHPer生存戦略 ~「言語、もうなんでもよくない?」に本気で向き合う~
vivion
0
400
SlackアプリとLambdaの 連携を構築した話
pawn_4_s
1
120
PHP に部分適用が来るぞ!……ところで何それ?おいしいの? #phpcon / phpcon-2026
shogogg
0
650
Apache Hive: Toward a Cloud Native Lakehouse
okumin
0
190
Claude CodeとAgentCore Gatewayを繋ぐ際の認証認可 / Authentication and authorization when connecting Claude Code with AgentCore Gateway
har1101
2
270
yield再入門 #phpcon
o0h
PRO
0
1.1k
Featured
See All Featured
Designing Experiences People Love
moore
143
24k
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
3
430
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
300
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
240
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
890
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.3k
Mind Mapping
helmedeiros
PRO
1
310
Into the Great Unknown - MozCon
thekraken
41
2.7k
Bash Introduction
62gerente
615
220k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
BBQ
matthewcrist
89
10k
Why Our Code Smells
bkeepers
PRO
340
58k
Transcript
@Megabits_mzq_jp STORES גࣜձࣾ SwiftUI ͱ Shader Λ׆༻ͨ͠ ָ͍͠ΦϯϘʔσΟϯάىಈը໘ͷ࡞
None
None
None
None
None
None
None
Image(.onboardingIcon) .resizable() .aspectRatio(1, contentMode: .fit) .frame(width: 300) .distortionEffect( ShaderLibrary.meltDistortion( .boundingRect,
.float(time) ), maxSampleOffset: .init(width: 150, height: 150)) .glur(radius: 8.0, offset: 0.4, interpolation: 0.5)
Image(.onboardingIcon) .resizable() .aspectRatio(1, contentMode: .fit) .frame(width: 300) .distortionEffect( ShaderLibrary.meltDistortion( .boundingRect,
.float(time) ), maxSampleOffset: .init(width: 150, height: 150)) .glur(radius: 8.0, offset: 0.4, interpolation: 0.5)
None
#include <metal_stdlib> using namespace metal; [[ stitchable ]] float2 meltDistortion(float2
position, float4 bounds, float time) { float2 positionYInSize = position / bounds.zw; if (positionYInSize.y > 0.5) { float y = positionYInSize.y - 0.5; position.x += sin(y * 25 + time) * 0.2 * bounds.z * y; } return position; }
#include <metal_stdlib> using namespace metal; [[ stitchable ]] float2 meltDistortion(float2
position, float4 bounds, float time) { float2 positionYInSize = position / bounds.zw; if (positionYInSize.y > 0.5) { float y = positionYInSize.y - 0.5; position.x += sin(y * 25 + time) * 0.2 * bounds.z * y; } return position; }
#include <metal_stdlib> using namespace metal; [[ stitchable ]] float2 meltDistortion(float2
position, float4 bounds, float time) { float2 positionYInSize = position / bounds.zw; if (positionYInSize.y > 0.5) { float y = positionYInSize.y - 0.5; position.x += sin(y * 25 + time) * 0.2 * bounds.z * y; } return position; }
#include <metal_stdlib> using namespace metal; [[ stitchable ]] float2 meltDistortion(float2
position, float4 bounds, float time) { float2 positionYInSize = position / bounds.zw; if (positionYInSize.y > 0.5) { float y = positionYInSize.y - 0.5; position.x += sin(y * 25 + time) * 0.2 * bounds.z * y; } return position; }
#include <metal_stdlib> using namespace metal; [[ stitchable ]] float2 meltDistortion(float2
position, float4 bounds, float time) { float2 positionYInSize = position / bounds.zw; if (positionYInSize.y > 0.5) { float y = positionYInSize.y - 0.5; position.x += sin(y * 25 + time) * 0.2 * bounds.z * y; } return position; }
None
None
TimelineView(.animation) { timeline in let time = startDate.distance(to: timeline.date) Image(.onboardingIcon)
.resizable() .aspectRatio(1, contentMode: .fit) .frame(width: 300) .distortionEffect( ShaderLibrary.meltDistortion( .boundingRect, .float(time) ), maxSampleOffset: .init(width: 150, height: 150)) .glur(radius: 8.0, offset: 0.4, interpolation: 0.5) }
TimelineView(.animation) { timeline in let time = startDate.distance(to: timeline.date) Image(.onboardingIcon)
.resizable() .aspectRatio(1, contentMode: .fit) .frame(width: 300) .distortionEffect( ShaderLibrary.meltDistortion( .boundingRect, .float(time) ), maxSampleOffset: .init(width: 150, height: 150)) .glur(radius: 8.0, offset: 0.4, interpolation: 0.5) }
None
None
None
Rectangle() .background { Color.white.opacity(0.1) } .foregroundStyle(stripeColor) .colorEffect( ShaderLibrary.stripBackground( .boundingRect, .float(stripeCount)
) ) .colorEffect( ShaderLibrary.grain( .boundingRect, .float(0) ) ) .opacity(0.3) .overlay(alignment: .top) { LinearGradient(colors: [.black, .clear], startPoint: .top, endPoint: .bottom) .frame(height: 50) }
None
#include <metal_stdlib> using namespace metal; float2 rotateUV(float2 uv, float rotation)
{ float mid = 0.5; return float2( cos(rotation) * (uv.x - mid) + sin(rotation) * (uv.y - mid) + mid, cos(rotation) * (uv.y - mid) - sin(rotation) * (uv.x - mid) + mid ); } [[ stitchable ]] half4 stripBackground(float2 position, half4 color, float4 bounds, float stripCount) { float2 positionInSize = position/bounds.zw; float2 rotated = rotateUV(positionInSize, 1.2); float stripPosition = fmod(rotated.x * stripCount,1); half a = smoothstep(0, 1, stripPosition * 2); if (stripPosition > 0.5) { a = smoothstep(1, 0, (stripPosition - 0.5) * 2); } half alpha = smoothstep(0.8, 0, rotated.y); return half4(color.rgb * a * alpha, alpha); }
#include <metal_stdlib> using namespace metal; float2 rotateUV(float2 uv, float rotation)
{ float mid = 0.5; return float2( cos(rotation) * (uv.x - mid) + sin(rotation) * (uv.y - mid) + mid, cos(rotation) * (uv.y - mid) - sin(rotation) * (uv.x - mid) + mid ); } [[ stitchable ]] half4 stripBackground(float2 position, half4 color, float4 bounds, float stripCount) { float2 positionInSize = position/bounds.zw; float2 rotated = rotateUV(positionInSize, 1.2); float stripPosition = fmod(rotated.x * stripCount,1); half a = smoothstep(0, 1, stripPosition * 2); if (stripPosition > 0.5) { a = smoothstep(1, 0, (stripPosition - 0.5) * 2); } half alpha = smoothstep(0.8, 0, rotated.y); return half4(color.rgb * a * alpha, alpha); }
#include <metal_stdlib> using namespace metal; float2 rotateUV(float2 uv, float rotation)
{ float mid = 0.5; return float2( cos(rotation) * (uv.x - mid) + sin(rotation) * (uv.y - mid) + mid, cos(rotation) * (uv.y - mid) - sin(rotation) * (uv.x - mid) + mid ); } [[ stitchable ]] half4 stripBackground(float2 position, half4 color, float4 bounds, float stripCount) { float2 positionInSize = position/bounds.zw; float2 rotated = rotateUV(positionInSize, 1.2); float stripPosition = fmod(rotated.x * stripCount,1); half a = smoothstep(0, 1, stripPosition * 2); if (stripPosition > 0.5) { a = smoothstep(1, 0, (stripPosition - 0.5) * 2); } half alpha = smoothstep(0.8, 0, rotated.y); return half4(color.rgb * a * alpha, alpha); }
#include <metal_stdlib> using namespace metal; float2 rotateUV(float2 uv, float rotation)
{ float mid = 0.5; return float2( cos(rotation) * (uv.x - mid) + sin(rotation) * (uv.y - mid) + mid, cos(rotation) * (uv.y - mid) - sin(rotation) * (uv.x - mid) + mid ); } [[ stitchable ]] half4 stripBackground(float2 position, half4 color, float4 bounds, float stripCount) { float2 positionInSize = position/bounds.zw; float2 rotated = rotateUV(positionInSize, 1.2); float stripPosition = fmod(rotated.x * stripCount,1); half a = smoothstep(0, 1, stripPosition * 2); if (stripPosition > 0.5) { a = smoothstep(1, 0, (stripPosition - 0.5) * 2); } half alpha = smoothstep(0.8, 0, rotated.y); return half4(color.rgb * a * alpha, alpha); }
None
Rectangle() .background { Color.white.opacity(0.1) } .foregroundStyle(stripeColor) .colorEffect( ShaderLibrary.stripBackground( .boundingRect, .float(stripeCount)
) ) .colorEffect( ShaderLibrary.grain( .boundingRect, .float(0) ) ) .opacity(0.3) .overlay(alignment: .top) { LinearGradient(colors: [.black, .clear], startPoint: .top, endPoint: .bottom) .frame(height: 50) }
#include <SwiftUI/SwiftUI_Metal.h> #include <metal_stdlib> using namespace metal; [[ stitchable ]]
half4 grain(float2 position, half4 color, float4 bounds, float time) { float strength = 16.0; float2 coords = position / bounds.zw; float x = (coords.x + 4.0 ) * (coords.y + 4.0 ) * 10.0; float4 grain = float4(fmod((fmod(x, 13.0) + 1.0) * (fmod(x, 123.0) + 1.0), 0.01)-0.005) * strength; return color + half4(grain); }
None
None
None
None
TimelineView(.animation) { timeline in Color.clear .onChange(of: timeline.date) { old, new
in if allowInteraction { let dateDiff = old.distance(to: new) if !isPressing { slitPosition += dateDiff / 2 if slitPosition > 1 { slitPosition = 1 } } else { slitPosition -= dateDiff / 3 if slitPosition < -0.1 { allowInteraction = false viewObject.successHaptics() nextPage?() } } } if slitPosition != 1 { let newValue = max(Float(slitPosition * viewSize.height), 1) let oldValue = slitScanData.first ?? 1 let diff = Int(abs(newValue - oldValue)) if diff != 0 { if newValue > oldValue { for i in 1...diff { slitScanData.insert(oldValue + Float(i), at: 0) } } else { for i in 1...diff { slitScanData.insert(oldValue - Float(i), at: 0) } } } else { slitScanData.insert(newValue, at: 0) } if slitScanData.count > Int(viewSize.height / 2) { slitScanData.removeLast() } } } }
content .background { StripBackgroundView() .overlay(alignment: .top) { LinearGradient(colors: [.black, .clear],
startPoint: .top, endPoint: .bottom) .frame(height: 50) } } .layerEffect(ShaderLibrary.slitScanVerticalStacked( .float(max(slitPosition * viewSize.height, 0)), .floatArray(slitScanData) ), maxSampleOffset: .init(width: 0, height: viewSize.height / 2))
None
#include <metal_stdlib> #include <SwiftUI/SwiftUI.h> using namespace metal; [[ stitchable ]]
half4 slitScanVerticalStacked(float2 position, SwiftUI::Layer layer, float slitPosition, device const float *slitScanData, int count) { if (position.y < slitPosition) { return layer.sample(position); } else { float dataIndexToUse = position.y - slitPosition; return layer.sample(float2(position.x, slitScanData[int(dataIndexToUse / 2)])); } }
None
Rectangle() .foregroundStyle(.accent) .colorEffect( ShaderLibrary.drawScanLine( .boundingRect, .float(slitPosition), .float(0.003), .float((slitPosition < 0)
? -slitPosition*15 : 0.1), .float(0.4) ) )
#include <metal_stdlib> using namespace metal; [[ stitchable ]] half4 drawScanLine(float2
position, half4 color, float4 bounds, float linePosition, float lineWidth, float shadowWidth, float shadowAlpha) { float2 positionInSize = position/bounds.zw; if (positionInSize.y > linePosition && positionInSize.y < linePosition + lineWidth) { return color; } else if (positionInSize.y > linePosition && positionInSize.y < linePosition + shadowWidth) { float positionInShadow = (positionInSize.y - linePosition) / shadowWidth; half a = smoothstep(1.0, 0.0, positionInShadow) * shadowAlpha; return half4(color.rgb * a, a); } else { return half4(0); } }
None
None
@Megabits_mzq_jp GitHub SLIT_STUDIO