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
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
はてなアカウント基盤 State of the Union
cockscomb
1
930
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
14
6.4k
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
2.3k
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
410
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
1
310
Webフレームワークの ベンチマークについて
yusukebe
0
180
act1-costs.pdf
sumedhbala
0
120
1B+ /day規模のログを管理する技術
broadleaf
0
120
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
220
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
7.1k
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
610
Vite+ Unified Toolchain for the Web
naokihaba
0
360
Featured
See All Featured
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
170
Skip the Path - Find Your Career Trail
mkilby
1
150
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
Believing is Seeing
oripsolob
1
160
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
210
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.3k
Thoughts on Productivity
jonyablonski
76
5.2k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
370
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
170
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
280
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
350
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.2k
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