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
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
160
Liquid Glass, どこが変わったのか
megabitsenmzq
0
170
iPhone 16 Camera Control
megabitsenmzq
0
150
240fps で画像処理したい
megabitsenmzq
0
240
Swift 開発が楽になる道具たち
megabitsenmzq
1
780
Animoji を作ってみた
megabitsenmzq
0
210
MainMenu.xib を翻訳してみた
megabitsenmzq
0
300
WKWebView とめんどくさいお友達
megabitsenmzq
1
770
先週解決した SwiftUI 問題
megabitsenmzq
0
140
Other Decks in Programming
See All in Programming
3Dシーンの圧縮
fadis
1
650
LLM Plugin for Node-REDの利用方法と開発について
404background
0
160
Why Laravel apps break—Mastering the fundamentals to keep them maintainable
kentaroutakeda
1
340
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
310
net-httpのHTTP/2対応について
naruse
0
440
TypeSpec で繋ぐ複数プロダクトの型安全
maroon8021
1
380
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
190
Composerを使ったサプライチェーン攻撃の様子を眺めてみる #phpstudy
o0h
PRO
2
220
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
1.1k
SPMマルチモジュールで テストカバレッジを取得する技法
yosshi4486
0
140
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
230
Swiftのレキシカルスコープ管理
kntkymt
0
210
Featured
See All Featured
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
55k
Building Flexible Design Systems
yeseniaperezcruz
330
40k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.5k
Designing Powerful Visuals for Engaging Learning
tmiket
1
400
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
122
22k
The browser strikes back
jonoalderson
0
1.1k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
370
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.1k
Product Roadmaps are Hard
iamctodd
PRO
55
12k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
290
Technical Leadership for Architectural Decision Making
baasie
3
400
The Mindset for Success: Future Career Progression
greggifford
PRO
0
350
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