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
170
Liquid Glass, どこが変わったのか
megabitsenmzq
0
180
iPhone 16 Camera Control
megabitsenmzq
0
160
240fps で画像処理したい
megabitsenmzq
0
240
Swift 開発が楽になる道具たち
megabitsenmzq
1
790
Animoji を作ってみた
megabitsenmzq
0
210
MainMenu.xib を翻訳してみた
megabitsenmzq
0
310
WKWebView とめんどくさいお友達
megabitsenmzq
1
780
先週解決した SwiftUI 問題
megabitsenmzq
0
160
Other Decks in Programming
See All in Programming
Prismを使った型安全な暗号化_関数型まつり2026
_fhhmm
0
150
【やさしく解説 設計編・中級 #4】ルールの寿命と、システムの年輪
panda728
PRO
2
160
初めてのKubernetes 本番運用でハマった話
oku053
0
130
【SRE NEXT 2026 Lunch Session】一人目専任SREの立ち上げを加速する ― AIと進めたオンボーディングで2分を0.04秒にした話
pkshadeck
PRO
0
2.9k
AIキャラアプリkaiwaの低遅延音声通話基盤をどう作ったか - AWS Gravitonで支える低遅延・低コストAI Agent基盤
mogamit
0
180
Embedded SREと共に達成した会員管理システムのAWS移行 - SRE NEXT 2026 ランチスポンサーセッション
niftycorp
PRO
1
2.9k
ソフトウェア設計に溶けるインフラ ― AWS CDK のインフラ認識論
konokenj
2
620
Built Our Own Background Agent at LayerX #aidevex_findy
layerx
PRO
8
3k
act1-costs.pdf
sumedhbala
0
240
Terraform標準の組織で AWS CDKをどう使うか
mu7889yoon
1
340
1年で人数1.5倍、PR数5.5倍増。 品質とアウトカムはどうなったか、 何が効いたか
ike002jp
0
140
言語を使う側から、作る側へ。 自作 Lisp で得た新たな気づき。
andpad
0
130
Featured
See All Featured
ラッコキーワード サービス紹介資料
rakko
1
4M
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
220
4 Signs Your Business is Dying
shpigford
187
22k
The untapped power of vector embeddings
frankvandijk
2
1.8k
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.8k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.3k
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
410
Rails Girls Zürich Keynote
gr2m
96
14k
Believing is Seeing
oripsolob
1
170
Claude Code のすすめ
schroneko
67
230k
Mobile First: as difficult as doing things right
swwweet
225
10k
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