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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Megabits_mzq
September 03, 2025
Programming
130
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
170
Liquid Glass, どこが変わったのか
megabitsenmzq
0
180
iPhone 16 Camera Control
megabitsenmzq
0
160
240fps で画像処理したい
megabitsenmzq
0
240
Swift 開発が楽になる道具たち
megabitsenmzq
1
780
Animoji を作ってみた
megabitsenmzq
0
210
MainMenu.xib を翻訳してみた
megabitsenmzq
0
300
WKWebView とめんどくさいお友達
megabitsenmzq
1
780
先週解決した SwiftUI 問題
megabitsenmzq
0
150
Other Decks in Programming
See All in Programming
Dataformのリポジトリを立ち上げるときにまずやること / dataform-day0-2026
snhryt
0
190
「なぜそう決めたのか」を残し続ける仕組み ― Notion AI カスタムエージェント × Slack連携による設計判断の自動記録 - NIKKEI Tech Talk #47
niftycorp
PRO
0
230
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
600
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
810
AI時代のUIはどこへ行く?その2!
yusukebe
22
7.5k
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
5.4k
メソッドのジェネリクスでGoの夢は広がるか? / Kyoto.go #65
utgwkk
3
980
任せる範囲はこう広がった / How the Scope of AI Delegation Has Expanded
nrslib
0
160
技術記事、 専門家としてのプログラマ、 言語化
mizchi
13
6.6k
決定論的オーケストレーションの設計と実装 / Design and Implementation of Deterministic Orchestration
nrslib
4
1.5k
OSもどきOS
arkw
0
590
どこまでゆるくて許されるのか
tk3fftk
0
260
Featured
See All Featured
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Six Lessons from altMBA
skipperchong
29
4.3k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.2k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
We Are The Robots
honzajavorek
0
260
Building Flexible Design Systems
yeseniaperezcruz
330
40k
Believing is Seeing
oripsolob
1
160
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
210
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
850
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
310
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
160
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
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