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
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
320
Between Models and Reality
mayunak
4
360
Everyday Curiosity
cassininazir
0
250
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.7k
WENDY [Excerpt]
tessaabrams
11
38k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.8k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
870
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
So, you think you're a good person
axbom
PRO
2
2.1k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.1k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
1
2.8k
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_