Swift Widget
๊ฐ๋จ ์ฌ์ฉ๋ฒ
File โ New โ Target โ Widget Extension ์์ฑ
โข
Live Activity(Dynamic island) ์ฌ๋ถ์, Intent ๊ตฌ์ฑ(๊ธฐ์ด ์ค์ )์ ํฌํจํ ๊ฒ์ธ์ง๋ ์ค์ ๊ฐ๋ฅ
โข
scheme ๋ํ ์ถ๊ฐ ๊ฐ๋ฅ
ย ์์ ฏ ํด๋ ํํค์น๊ธฐ
1. Bundle (MyWidgetBundle.swift)
// MyWidgetBundle.swift
import WidgetKit
import SwiftUI
@main
struct MyWidgetBundle: WidgetBundle {
var body: some Widget {
MyWidget()
}
}
Swift
๋ณต์ฌ
โข
Widget ํ๋กํ ์ฝ์ ์ฑํํ ๊ตฌ์กฐ์ฒด๋ฅผ ๋ฃ์ด์ค ์ ์๋ค.
โข
์ฌ๋ฌ ์ ํ์ ์์ ฏ์ ์ง์ํ๋ ค๋ฉด ์ฌ๊ธฐ์ ์์ฑ์ ์ถ๊ฐํด์ค ์ ์๋ค.
๊ณต์๋ฌธ์ ์)
๊ฒ์์๋ ๊ฒ์์ ๋ํ ์์ฝ ์ ๋ณด๋ฅผ ํ์ํ๋ ์์ ฏ ํ๋,
๊ฐ๋ณ ์บ๋ฆญํฐ์ ๋ํ ๋ํ
์ผํ ์ ๋ณด๋ฅผ ํ์ํ๋ ์์ ฏ ํ๋,
์ด๋ ๊ฒ WidgetBundle์ ์์ ์ ์๋ค.
@main
struct GameWidgets: WidgetBundle {
var body: some Widget {
GameStatusWidget()
CharacterDetailWidget()
}
}
Swift
๋ณต์ฌ
2. MyWidget
ํด๋น ํ์ผ๋ก ๋ค์ด๊ฐ๋ณด๋ฉด ๋ค์ํ ๊ตฌ์กฐ์ฒด๋ค์ด ์ถ๊ฐ๊ฐ ๋์ด์๋ค.
struct Provider: IntentTimelineProvider {}
struct SimpleEntry: TimelineEntry {}
struct MyWidgetEntryView: View {}
struct MyWidget: Widget {}
Swift
๋ณต์ฌ
Provider
โข
์์ ฏ์ ์ปจํ
์ธ ๋ฅผ ์
๋ฐ์ดํธํ ๋ ์ง์ ์๊ฐ์ ์ง์ ํ๊ณ , ์์ ฏ์ ๋ ๋๋งํ๋ ๋ฐ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ํฌํจํ๋ค.
์ธ ๊ฐ์ง ํจ์๊ฐ ์กด์ฌ
โข
placeholder
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationIntent())
}
Swift
๋ณต์ฌ
โฆ
์์ ฏ์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ธฐ ์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ๋ฉด์ ๋ณด์ฌ์ฃผ์ฌ์ผ ํ ๊ฐ๋ค์ ์ค์ ํ๋ ํจ์
โฆ
ํ์ฌ ์๊ฐ์ ๋ํ date์ configuration์ ์์ฑํด ๋ณด์ฌ์ฃผ๊ณ ์๋ ๋ชจ์ต
โข
getShapshot
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), configuration: configuration)
completion(entry)
}
Swift
๋ณต์ฌ
โฆ
์์ ฏ์ ๊ณ ๋ฅผ ๋ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์์ ๋ณด์ฌ์ง๋ ๋ฐ์ดํฐ๋ค์ ์ค์ ํ๋ ๋จ๊ณ
โฆ
์ด๋ฏธ ๋ํ๋ ๊ฒฝ์ฐ completion์ ํธ์ถํ๊ณ , ์์ ฏ์ ํ์ฌ ์ํ๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋ ์ค๋ ๊ฑธ๋ฆด ๊ฒฝ์ฐ, ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํด ๋ณด์ฌ์ค ์๋ ์๋ค.
โข
getTimeline
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
for hourOffset in 0..<4 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, configuration: configuration)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
Swift
๋ณต์ฌ
โฆ
ํ์ฌ ์๊ฐ๊ณผ ์ ํ์ ์ผ๋ก ์์ ฏ์ ์
๋ฐ์ดํธ ํ ๋ ๋ฏธ๋์ ์๊ฐ์ ๋ํ ํ์๋ผ์ธ ํญ๋ชฉ ๋ฐฐ์ด์ ์ ๊ณตํ๋ค.
โฆ
์
๋ฐ์ดํธ๋ฅผ ์ธ์ ์งํํ ๊ฒ์ธ์ง์ ๋ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๋ฉ์๋
ํ์ฌ ์ฝ๋๋ 0~3์ ํฌํจํ ๋ฐ๋ณต ํ์ ๋งํผ์ ์๊ฐ์ entry์ ๋ด๊ณ ์ด๋ฅผ Timeline์ผ๋ก ๋ง๋ค์ด ์
๋ฐ์ดํธ๋ฅผ ์งํํ๋ ์ฝ๋์ด๋ค.
์ฆ๊ฐ์ ์ผ๋ก ๋ฐ์ํ๋ ์์ ฏ์ ๋ง๋ค๊ณ ์ถ์ง๋ง ๊ทธ๋ด ์ ์๋ค,, ์?
์์ ฏ์ด ํ๋ฉด์ ํ์๋์ด ์์ด๋ ์์ ฏ์ ๊ณ์ ํ์ฑํ ๋์ง ์๋๋ค.
์ ํด์ง ์์ฐ ๋ด์์์ ๋ฆฌ๋ก๋ฉ ๊ณํ์ด ํ์์ ์ผ๋ก ๋ณด์.
์์ ฏ์ ๋ค์ ๋ก๋ํ๋ฉด ์์คํ
๋ฆฌ์์ค๊ฐ ์๋ชจ๋๊ณ , ๋คํธ์ํฌ ์ฒ๋ฆฌ๊ฐ ์ถ๊ฐ์ ์ผ๋ก ๋ค์ด๊ฐ ์๋ค๋ฉด ๋ฐฐํฐ๋ฆฌ ์๋ชจ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค.
๋ฐ๋ผ์ ์ด๋ฌํ ์ฑ๋ฅ ์ํฅ์ ์ค์ด๊ณ , ๋ฐฐํฐ๋ฆฌ ์๋ช
์ ํ๋ฃจ์ข
์ผ ์ ์งํ๊ธฐ ์ํด์๋ ์์ฒญํ๋ ์
๋ฐ์ดํธ์ ๋น๋์ ํ์๋ฅผ ์ ํํด์ผ ํ๋ค.
WidgetKit์ ์์คํ
๋ก๋๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด ์ ํด์ง ์์ฐ์ ์ฌ์ฉํ์ฌ ํ๋ฃจ ๋์ ์์ ฏ์ ๋ค์ ๋ก๋ฉํ๊ณ ๋ฐฐํฌํ๋ ๊ณผ์ ์ ๊ฑฐ์น๋ค.
์ด ์์ฐ์ด ์ผ๋ง๋ ์ฃผ์ด์ง์ง๋ ๋์ ์ผ๋ก ๊ณ์ฐ๋๋ฉฐ, ๋ค์์ ํฌํจํ๋ ๋ค์ํ ์์๋ฅผ ๊ณ ๋ คํด์ผ ํ ๋น์ด ๋๋ค.
- ์์ ฏ์ด ์ฌ์ฉ์์๊ฒ ํ์๋๋ ๋น๋์ ์๊ฐ
- ์์ ฏ์ ๋ง์ง๋ง ์๋ก๊ณ ์นจํ ์๊ฐ
- ์์ ฏ์ด ํฌํจ๋ ์ฑ์ด ํ์ฑ ์ํ์ธ์ง
ํ ๋น๋ ์์ ฏ์ ์์ฐ์ 24์๊ฐ ๋์๋ง ์ ์ฉ๋๋ค.
๋ํ ์ฌ์ฉ์์ ์ผ์ผ ์ฌ์ฉ ํจํด์ ๋ง๊ฒ ์กฐ์ ํ๊ฒ ๋๋ค.
๊ทธ๋ ๋ค๊ณ ํด์ ์์ฐ์ด ์ ํํ ์์ ์ ์ด๊ธฐํ ๋๋ ๊ฒ์ ์๋๋ฐ, ์ ํ์ ๋ฐ๋ฅด๋ฉด ์ฌ์ฉ์๊ฐ ์์ฃผ ๋ณด๋ ์์ ฏ์ ๊ฒฝ์ฐ,
์ผ์ผ ์์ฐ์๋ ๋ณดํต 40~70ํ์ ์๋ก๊ณ ์นจ์ด ํฌํจ๋๋ค๊ณ ํ๋ค.
๋๋ต์ ์ผ๋ก 15๋ถ~60๋ถ ๋ง๋ค ์์ ฏ์ ๋ค์ ์๋ก๊ณ ์นจ ํ๋ ๊ฒ์ผ๋ก ํด์์ด ๋์ง๋ง, ์ฌ๋ฌ ์์ธ์ผ๋ก ์ธํด ๊ฐ๊ฒฉ์ด ๋ฌ๋ผ์ง๋ค.
โ ์์คํ
์ด ์ฌ์ฉ์์ ํ๋์ ํ์ตํ๋ ๊ฒ์๋ ์๊ฐ์ด ๊ฑธ๋ฆฐ๋ค.
โ ์ด ํ์ต ๊ธฐ๊ฐ ๋์ ์์ ฏ์ ํ์๋ณด๋ค ๋ ๋ง์ ์๋ก๊ณ ์นจ์ ์งํํ ์ ์๋ค.
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
for hourOffset in 0..<4 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
// ํ์ฌ ์๊ฐ์ value ๋งํผ์ ์๊ฐ์ ๋ํ ๊ฐ์(0~3)
let entry = SimpleEntry(date: entryDate, configuration: configuration)
entries.append(entry)
// ์ด entry ๊ฐ์ฒด์ ๋ฃ์ด์ค ํ ๋ฐฐ์ด์ append ์์ผ์ค๋ค.
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
// ์ดํ entries ๋ฐฐ์ด์ ํตํด Timeline์ ๋ง๋ค๊ณ completion์ ํตํด ํธ์ถํ๋ค.
// .atEnd์ ํตํด ๋ง์ง๋ง date๊ฐ ๋๋ ํ, ํ์๋ผ์ธ์ reload ํด์ค ์ ์๋ค.
}
Swift
๋ณต์ฌ
โฆ
๊ทธ๋ฆผ์ ๋ณด๋ฉด ์ด๊ธฐ ์์ ฏ์ด ํ๋ฉด์ ๋ณด์ฌ์ง๊ณ , ์ด๋ฅผ reloadํด์ Provide timeline์ผ๋ก ํ๋ฌ๋ค์ด์จ๋ค.
โฆ
์ฐ๋ฆฌ์ ๋ฐฐ์ด์ ์ด์ฐฝ๊ธฐ now ๊ฐ์ 0์ ๋ํ ๊ฐ์ธ ํ์ฌ์๊ฐ์ด ๋๋ค.
โฆ
๋ฐฐ์ด์ ๋ง์ง๋ง๋ฒ ์งธ์ ๋๋ฌํ๊ธฐ ๋๋ฌธ์ Provider๋ ํ์ฌ ์๊ฐ์ ๋ํ ๋จ์ผ ํญ๋ชฉ์ผ๋ก ์ค์ ํธ๋ ์๋ก๊ณ ์นจ ์ ์ฑ
์ผ๋ก ์๋ตํ๊ฒ ๋๋ค.
โฆ
๋ง๋ฌด๋ฆฌ ๋๋ฉด ์ ํ์๋ผ์ธ์ ์์ฒญํ๋๋ก ์ง์ํ ๋๊น์ง ๋ค๋ฅธ ํ์๋ผ์ธ์ ์์ฒญํ์ง ์๋๋ค.
โข
์๋ก์ด ํ์๋ผ์ธ๋ ๋ง๋ค ์ ์๋ค.
โฆ
์) ์ฃผ์
โฆ
์ฃผ์ ๊ฐ์ฅ์ ์ฃผ๋ง์๋ ์งํํ์ง ์๋๋ค. ๋ฐ๋ผ์ ๊ธ์์ผ์ ์ฅ ์๊ฐ ๋ง์ง๋ง๋ ์ ๋ ์ง๋ฅผ ์ง์ ํด์ ์ฃผ๋ง์ ์คํตํ ์ ์๋ค.
โฆ
์ด๋ฌํ ๊ฒฐ๊ณผ๋ก ์ฃผ์์์ฅ์ด ์ด๋ฆด ๋๊น์ง ์์ ฏ์ ์
๋ฐ์ดํธํ ํ์๊ฐ ์๋ค.
let timeline = Timeline(entries: entries, policy: .after(date: Date))
Swift
๋ณต์ฌ
โฆ
๊ธฐ์กด .atEnd ์์ .after๋ก ๋ฐ๊ฟ์ค๋ค๋ฉด ํด๋น Date ๋ ์ง๊ฐ ์ง๋ ํ ํ์๋ผ์ธ์ ๋ฆฌ๋ก๋ฉํ๊ฒ ์์ ํ ์ ์๋ ๊ฒ
SimpleEntry
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
}
Swift
๋ณต์ฌ
โข
๊ธฐ๋ณธ์ ์ผ๋ก TimelineEntry ํ๋กํ ์ฝ์ ์ฑํํด์ผ ํ๋ค.
โข
ํ์ ๊ตฌํ ํ๋กํผํฐ๋ก๋ date๊ฐ ์๋ค.
โข
์์ ฏ์ ํ์ํ ๋ ์ง๋ฅผ ์ง์ ํ๊ณ ํ์๋ผ์ธ ํญ๋ชฉ์ ์์ฑํ๊ธฐ ์ํด ํ์ํ๋ค. configuration์ ํ์๋ก ๊ตฌํํ์ง ์์๋ ๋๋ค.
โข
์์ ฏ extension์ ํ ๋, Configuration Intent๋ฅผ ์ฒดํฌ ํ๋ค๋ฉด ์๋์ ์ผ๋ก ๋ฐ๋ผ ๋ถ๊ฒ ๋๋ค. ํด๋น ํ๋กํผํฐ๋ ๋์ ์ผ๋ก ๋ทฐ๋ฅผ ๊ตฌ์ฑํ ๋ ํ์ํ๋ค.
WidgetEntryView
struct MyWidgetEntryView: View {
@Environment(\.wigetFamily) var family: WidgetFamily
var entry: Provider.Entry
var body: some View {
switch family {
case .systemSmall:
SmallView()
case .systemMedium:
MediumView()
case .systemLarge:
LargeView()
default:
Text("None")
}
}
}
Swift
๋ณต์ฌ
โข
๋ ๋๋ง ๋์ด์ ์ค์ ๋ก ๋ณด์ฌ์ค ๋ทฐ๋ฅผ ๊ตฌ์ฑํ๋ ๊ตฌ์กฐ์ฒด
โข
WidgetFamily ๋ผ๋ ๊ฒ์ ํตํด ๋ค์ํ ์ฌ์ด์ฆ์ ๋ทฐ๋ฅผ ๊ตฌ์ฑํ ์ ์๋ค.
โข
์ฌ๊ธฐ์ ์ํธ๋ฆฌ๋ฅผ ๋ฐ์ ํด๋น ๋ทฐ์ ์ฃผ์
ํ ์ ์๋ค.
Widget
struct MyWidget: Widget {
let kind: String = "MyWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example Widget.")
}
}
Swift
๋ณต์ฌ
โข
kind์๋ ์์ ฏ์ id๋ฅผ ์์ฑํ ์ ์๋ค.
โข
intent์๋ ์ด๋ค ๊ฒ์ ๋์ ์ผ๋ก ์๋ํ๋์ง์ ๋ํด ์ค์ ํ ์ ์๋ค.
โข
provider์๋ ์์์ ๋งํ Provider๋ฅผ ์ฃผ์
์ํค๊ณ , entry ๋ํ ์ฃผ์
์ํฌ ์ ์๋ค.
โข
configurationDisplayName์ ํตํด ์์ ฏ ๊ฐค๋ฌ๋ฆฌ๋ผ๊ณ ๋ถ๋ฆฌ๋ ์์ ฏ ์ถ๊ฐํ๋ ํ๋ฉด์์์ ์๋จ์ ์์นํ ํ์ดํ ์ด๋ฆ์ ์ค์ ํ ์ ์๋ค.
โข
description ๋ํ ๋ง์ฐฌ๊ฐ์ง๋ก ํด๋น ํฌ๊ธฐ์ ์์ ฏ์ ๋ํ ์ค๋ช
๋๋ ์ ์ญ์ ์ธ ์ค๋ช
์ ์ถ๊ฐํ ์ ์๋ค.
entry๋ฅผ ์์ฑํ ๋, configuration์ ๋ดค์ ํ
๋ฐ, ํด๋น ๋ค์ด๋ฐ์ ์ ํ์ผ์์ ํ์๋์ด ๋์ค๊ฒ ๋๋ค.