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
Real-Time Countdown Widget
Search
DevJonny
October 21, 2025
400
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Real-Time Countdown Widget
DevJonny
October 21, 2025
More Decks by DevJonny
See All by DevJonny
Machine Learning with Create ML
devjonny
0
230
Featured
See All Featured
Odyssey Design
rkendrick25
PRO
2
720
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
190
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
72
40k
Designing for Performance
lara
611
70k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
250
1.3M
Automating Front-end Workflow
addyosmani
1370
210k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
180
Practical Orchestrator
shlominoach
191
11k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
1.4k
Build your cross-platform service in a week with App Engine
jlugia
234
18k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.8k
For a Future-Friendly Web
brad_frost
183
10k
Transcript
Real-Time Countdown Wigdet Jonny, Mobile Engineer @ GMOϟϖϡ
Introduce • ᷠࠃᲇᙻ • ࿆၀4ᢉ͝Λ᥀ᏣᫍΑ • ϧϕΩϰέϸνϐΧ |Suzuri} @ GMO
ϟϖϡ • Apple Developer Academy @ POSTECH • ൃද༻ͷຊޠ͕·ͩԼखͰ͢
None
Widgetを作りたいです! 👤 どんなWidget? 日 付を設定したら残りの時間が表 示 されるWidgetです! 👤 お! 日
数だけ表 示 されればいいんですよね? いいえ!秒まで秒までライブで表 示 されるものです! 👤 ???????
None
やってみます
None
func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline<SimpleEntry>
{ var entries: [SimpleEntry] = [] let targetDate = SharedStorage.loadTargetDate() let currentDate = Date() for secondOffset in 0 ..< 60 { let entryDate = Calendar.current.date(byAdding: .second, value: secondOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, targetDate: targetDate, configuration: configuration) entries.append(entry) } return Timeline(entries: entries, policy: .atEnd) }
var remainingTime: (days: Int, hours: Int, minutes: Int, seconds: Int)?
{ guard let targetDate = SharedStorage.loadTargetDate() else { return nil } let interval = targetDate.timeIntervalSince(entry.date) guard interval > 0 else { return nil } let totalSeconds = Int(interval) let days = totalSeconds / 86400 let hours = (totalSeconds % 86400) / 3600 let minutes = (totalSeconds % 3600) / 60 let seconds = totalSeconds % 60 return (days, hours, minutes, seconds) }
None
None
None
None
None
None
None
None
None
None
23時間 32分 24秒
23 : 32 : 24
None
None
23 : 32 : 24
23 : 32 : 24
23 : 32 : 24᰽
23 : 32 : 24᰽ 23 : 32 : 24
23 : 32 : 24᰽ 23 : 32 : 24
23 : 32 : 24᰽ 23 : 32 : 24
ᐠ
23 : 32 : 24᰽ 23 : 32 : 24
23 : 32 : 24᰽ 23 : 32 : 24
23 : 32 : 24᰽ 23 : 32 : 24
None
🟡: 今は正常に動作しているのですが、ある瞬間からエラ が発 生 してしまいます…。 ⚫: エラ が発 生 した時に、スクリ
ンショットを送っていただけますか? 🟡: エラ が発 生 すると、ウィジェット 自 体がレンダリングされなくなって、 画 面 が黒、または 白 く表 示 されます。 調べたところ、秒単位のシンプルなライブ動作は可能ですが、 このようにカスタムして動作させるためには、複数のデ タタイムラインを 読み込む必要があります。 しかし、Appleのシステムではタイムラインのリロ ドに 15 60分かかるようで、 たとえ最短の15分でも、その間に約900件のデ タが 読み込まれてしまいます。 その結果、おそらく動作が途中で 止 まってしまうのだと思います。
🟡: 今は正常に動作しているのですが、ある瞬間からエラ が発 生 してしまいます…。 ⚫: エラ が発 生 した時に、スクリ
ンショットを送っていただけますか? 🟡: エラ が発 生 すると、ウィジェット 自 体がレンダリングされなくなって、 画 面 が黒、または 白 く表 示 されます。 調べたところ、秒単位のシンプルなライブ動作は可能ですが、 このようにカスタムして動作させるためには、複数のデ タタイムラインを 読み込む必要があります。 しかし、Appleのシステムではタイムラインのリロ ドに 15 60分かかるようで、 たとえ最短の15分でも、その間に約900件のデ タが 読み込まれてしまいます。 その結果、おそらく動作が途中で 止 まってしまうのだと思います。
None
None
HStack (spacing: 0) { Spacer() VStack { Spacer() Text(endDate, style:
.timer) .monospacedDigit() .multilineTextAlignment(.trailing) .font(.custom("PretendardJP-Bold", size: 26)) .mask( HStack { Spacer() ZStack { Text("00").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("11").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("22").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("33").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("44").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("55").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("66").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("77").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("88").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("99").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) } } ) } VStack { Spacer() Text("᰽") .font(.custom("PretendardJP-Bold", size: 26)) } }
HStack (spacing: 0) { Spacer() VStack { Spacer() Text(endDate, style:
.timer) .monospacedDigit() .multilineTextAlignment(.trailing) .font(.custom("PretendardJP-Bold", size: 26)) .mask( HStack { Spacer() ZStack { Text("00").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("11").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("22").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("33").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("44").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("55").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("66").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("77").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("88").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("99").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) } } ) } VStack { Spacer() Text("᰽") .font(.custom("PretendardJP-Bold", size: 26)) } } 23 : 32 : 24
HStack (spacing: 0) { Spacer() VStack { Spacer() Text(endDate, style:
.timer) .monospacedDigit() .multilineTextAlignment(.trailing) .font(.custom("PretendardJP-Bold", size: 26)) .mask( HStack { Spacer() ZStack { Text("00").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("11").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("22").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("33").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("44").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("55").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("66").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("77").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("88").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("99").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) } } ) } VStack { Spacer() Text("᰽") .font(.custom("PretendardJP-Bold", size: 26)) } } 23 : 32 : 24 00
HStack (spacing: 0) { Spacer() VStack { Spacer() Text(endDate, style:
.timer) .monospacedDigit() .multilineTextAlignment(.trailing) .font(.custom("PretendardJP-Bold", size: 26)) .mask( HStack { Spacer() ZStack { Text("00").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("11").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("22").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("33").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("44").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("55").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("66").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("77").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("88").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("99").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) } } ) } VStack { Spacer() Text("᰽") .font(.custom("PretendardJP-Bold", size: 26)) } } 23 : 32 : 24 00 1 1
HStack (spacing: 0) { Spacer() VStack { Spacer() Text(endDate, style:
.timer) .monospacedDigit() .multilineTextAlignment(.trailing) .font(.custom("PretendardJP-Bold", size: 26)) .mask( HStack { Spacer() ZStack { Text("00").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("11").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("22").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("33").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("44").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("55").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("66").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("77").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("88").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("99").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) } } ) } VStack { Spacer() Text("᰽") .font(.custom("PretendardJP-Bold", size: 26)) } } 23 : 32 : 24 00 1 1 22
HStack (spacing: 0) { Spacer() VStack { Spacer() Text(endDate, style:
.timer) .monospacedDigit() .multilineTextAlignment(.trailing) .font(.custom("PretendardJP-Bold", size: 26)) .mask( HStack { Spacer() ZStack { Text("00").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("11").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("22").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("33").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("44").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("55").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("66").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("77").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("88").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("99").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) } } ) } VStack { Spacer() Text("᰽") .font(.custom("PretendardJP-Bold", size: 26)) } } 23 : 32 : 24 00 1 1 22 33
HStack (spacing: 0) { Spacer() VStack { Spacer() Text(endDate, style:
.timer) .monospacedDigit() .multilineTextAlignment(.trailing) .font(.custom("PretendardJP-Bold", size: 26)) .mask( HStack { Spacer() ZStack { Text("00").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("11").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("22").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("33").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("44").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("55").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("66").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("77").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("88").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("99").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) } } ) } VStack { Spacer() Text("᰽") .font(.custom("PretendardJP-Bold", size: 26)) } } 23 : 32 : 24 00 1 1 22 33 44 55 66 77 99 99
HStack (spacing: 0) { Spacer() VStack { Spacer() Text(endDate, style:
.timer) .monospacedDigit() .multilineTextAlignment(.trailing) .font(.custom("PretendardJP-Bold", size: 26)) .mask( HStack { Spacer() ZStack { Text("00").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("11").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("22").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("33").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("44").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("55").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("66").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("77").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("88").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("99").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) } } ) } VStack { Spacer() Text("᰽") .font(.custom("PretendardJP-Bold", size: 26)) } } 23 : 32 : 24 00 1 1 22 33 44 55 66 77 99 99
HStack (spacing: 0) { Spacer() VStack { Spacer() Text(endDate, style:
.timer) .monospacedDigit() .multilineTextAlignment(.trailing) .font(.custom("PretendardJP-Bold", size: 26)) .mask( HStack { Spacer() ZStack { Text("00").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("11").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("22").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("33").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("44").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("55").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("66").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("77").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("88").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) Text("99").monospacedDigit().font(.custom("PretendardJP-Bold", size: 26)) } } ) } VStack { Spacer() Text("᰽") .font(.custom("PretendardJP-Bold", size: 26)) } } 23 : 32 : 24
None
None
None
Thank you! Real-Time Countdown Wigdet JeEon Park / Jonny GMO
Pepabo X: @devjonny_