๐ ์ ๊ธํ๋ฉด ์์ ฏ ๋ง๋ค๊ธฐ - WidgetKit
๐ธ Live Activity ์ฌ์ฉํด ๋ณด๊ธฐ
์ด๋ฒ ๊ธ์์๋ WidgetKit์ ๋ํด์ ๋ค๋ค๋ณผ ์์ ์ด๋ค.
์ฃผ๋ก Flutter์ ๊ดํ ๊ธ๋ง ์์ฑํ๋ ํธ์ธ๋ฐ, ๋ค๋ฅธ ์ธ์ด๋ ํ๋ ์์ํฌ์ ๋ํด์ ์์ฑํด ๋์ง ์๋ค๋ณด๋ ๊ฐ๋ ์ฌ์ฉํ๋ ค๊ณ ํ ๋๋ง๋ค ์์ด๋ฒ๋ ค์ ์ ๋ฆฌํ๋ ์ฐจ์์์ ์์ฑ์ ํด๋ณด๋ ค๊ณ ํ๋ค.
WidgetKit์ด๋ iOS 14 ๋ฒ์ ๋ถํฐ ๋์ ๋ ํ๋ ์์ํฌ๋ก, ์ฌ์ฉ์๊ฐ ํ ํ๋ฉด ๋ฐ ์ ๊ธํ๋ฉด์์ ์์ฝ๊ฒ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์๋ ์์ ฏ์ ๊ฐ๋ฐํ๋๋ฐ ์ฌ์ฉ๋๋ ๊ธฐ๋ฅ์ด๋ค.
iOS 14 ์ดํ๋ฅผ ํ๊ฒ ํ๋ค๋ฉด ๋น์ฐํ ์์ธ์ฒ๋ฆฌ๋ฅผ ์งํํด ์ฃผ์ด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ด ๋ถ๋ถ์ ๋์น์ง ๋ง์์ผ ํ๋ค. ์ฌ๊ธฐ์๋ iOS 14 ์ด์๋ง ํ๊ฒํ๊ธฐ์ ๊ณ ๋ คํ์ง ์์ ์์ ์ด๋ค.
WidgetKit์์ ์์ ฏ์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ด ๋ ๊ฐ์ง๊ฐ ์๋ค.
Static Configuration๊ณผ Intent Configuration์ด๋ค.
๋จผ์ Static์ ๋ํด์ ์ดํด๋ณด์๋ฉด, ์๋ฏธ ๊ทธ๋๋ก ๊ณ ์ ๋ ์์ ฏ์ด๋ค.
์์ ฏ์ ๋ฐ์ดํฐ๊ฐ ๊ณ ์ ๋์ด์ ์ ์ ์ด๊ฑฐ๋ ์ฃผ๊ธฐ์ ์ผ๋ก ์ ๋ฐ์ดํธ๋ง ํ์ํ ๊ฒฝ์ฐ์ ์ฌ์ฉํ ์ ์๋ ์์ ฏ ์ข ๋ฅ์ด๋ค.
๊ทธ๋ ๋ค๋ฉด Intent๋ ๋น์ฐํ ๋ฐ๋ ๊ฐ๋ ์ผ ๊ฒ์ด๋ค.
์์ ฏ์ ๋ฐ์ดํฐ๊ฐ ๊ณ ์ ์ ์ด์ง ์๊ณ ๋ณ๊ฒฝํ ์ ์์ผ๋ฉฐ ์ฌ์ฉ์ ๊ฐ์ธํ๊ฐ ๊ฐ๋ฅํ ์์ ฏ์ ์ ์ฉํ๊ณ ์ถ์ ๋์ ์ฌ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋ค.
๊ธ๋ก ๋ณด๋ฉด ์ดํด๊ฐ ์๋ ์ ์์ง๋ง iOS์์๋ ์์ ฏ์ ํธ์ง ๊ธฐ๋ฅ์ด ์๋ ๊ฒฝ์ฐ์ ์๋ ๊ฒฝ์ฐ ๋ฑ ๋ ๊ฐ์ง๋ง ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ๊ทธ ์ฐจ์ด๋ผ๊ณ ๋ณด๋ฉด ๋๋ค.
์ฆ Static Configuration์ ์ ํ ๊ธฐ๋ณธ ์ฑ ์ค ๋ ์จ, ์บ๋ฆฐ๋, ๋ฐฐํฐ๋ฆฌ ์์ ฏ๊ณผ ๊ฐ์ด ์ ํด๋์ ๋ฐ์ดํฐ๋ง ์ ๋ฐ์ดํธํ์ฌ ๋ณด์ฌ์ง๊ฒ ๋๊ณ ๋ฐ๋ฉด ๋ฏธ๋ฆฌ์๋ฆผ, ๋จ์ถ์ด, ํ์บ์คํธ ์ฒ๋ผ ์์ ฏ์์ ๋ณด์ฌ์ง ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉ์๊ฐ ์ง์ ์ ํํ์ฌ ์์ ฏ์ ํธ์งํ ์ ์๋ ๋ฐฉ๋ฒ์ด Intent Configuration์ธ ๊ฒ์ด๋ค.
๋น์ฐํ Intent Configuration์ด ๊ตฌ์กฐ๊ฐ ๋ ๋ณต์กํ๊ณ ๊ตฌํํ๊ธฐ๊ฐ ์๋์ ์ผ๋ก ๋ณต์กํ๊ฒ ๋๋ค.
WidgetKit์ ์ฌ์ฉํ๊ธฐ ์์ ํต์ฌ ๊ตฌ์ฑ ์์์ ๋ํด์ ์ฐ์ ์์๋ณด๋๋ก ํ๊ฒ ๋ค.
TimelineProvider, Entry, Timeline ์ด๋ ๊ฒ 3 ๊ฐ์ง์ ํต์ฌ ๊ตฌ์ฑ ์์๊ฐ ์๊ณ , ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๊ณ ๊ด๋ฆฌํ๋๋ฐ ํต์ฌ์ ์ธ ์ญํ ์ ์ํํ๋ ์์ญ์ด๋ค.
์์ ฏ์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ ํ๋กํ ์ฝ๋ก, ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์ธ์ ํ์ํ ์ง๋ฅผ ์ ์ํ๋ ์์์ด๋ค.
placeholder
์์ ฏ์ ์ด๊ธฐ ์ํ๋ฅผ ๋ํ๋ด๋ฉฐ ๋คํธ์ํฌ ์์ฒญ์ ์์ฒญ์ด ์๋ฃ๋๊ธฐ ์ ๊น์ง ๋ณด์ฌ์ค ๊ธฐ๋ณธ ๋ฐ์ดํฐ๋ฅผ ์ค์ ํ๋ค.
getSnapshot
๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋๋ ๋น ๋ฅธ ์ ๋ฐ์ดํธ๋ฅผ ์ํด ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ฉฐ, ์์ ฏ์ ์ถ๊ฐํ๊ฑฐ๋ ์ค์ ์์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์์ ํธ์ถ๋๋ ๋ฉ์๋์ด๋ค.
getTimeline
์ผ์ ๊ฐ๊ฒฉ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ ๋๋ฅผ ํฌํจํ ํ์๋ผ์ธ์ ์ ๊ณตํ๋ฉฐ, ์ฌ๋ฌ TimelineEntry๋ฅผ ํฌํจํ Timeline ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ฐํํด ์ฃผ๊ธฐ์ ์ผ๋ก ์์ ฏ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
Entry๋ TimelineEntry ํ๋กํ ์ฝ์ ์ค์ํ๋ ๊ฐ์ฒด๋ก, ์์ ฏ์ ํ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ํ๋ด๋ฉฐ ์์ฑ๋ ๊ฐ Entry๋ ํน์ ์๊ฐ์ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ํ์ํ ์ง๋ฅผ ์ ์ํ๊ฒ ๋๋ค.
date ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํด ์๊ฐ์ ์์ฑํ ์ ์์ผ๋ฉฐ, ๋ฐ์ดํฐ ํ๋๋ฅผ ์ถ๊ฐํ์ฌ ๋ณด์ฌ์ค ๋ฐ์ดํฐ๋ฅผ ๋ํ๋ผ ์ ์๋ค.
์ฌ๋ฌ ๊ฐ์ Entry๋ก ๊ตฌ์ฑ๋ ๊ฐ์ฒด๋ก, ํน์ ์๊ฐ์ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ํ์ํ ์ง๋ฅผ ์ ์ํ๋๋ฐ ์ฌ์ฉ๋๋ค.
entries
TimelineEntry ๊ฐ์ฒด์ ๋ฐฐ์ด๋ก ๊ฐ Entry๋ ํน์ ์๊ฐ์ ํ์๋ ๋ฐ์ดํฐ์ ์๊ฐ์ ํฌํจํ๋ค.
reloadPolicy
๋ฆฌ๋ก๋ ๋์ด์ผํ๋ ์์ ์ ์ ์ํ๋ฉฐ .atEnd, .after, .never๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
์ด์ XCode์ ํ๋ก์ ํธ๋ฅผ ์์ฑํ ๋ค์ ๋ณธ๊ฒฉ์ ์ผ๋ก WidgetKit์ ์ฌ์ฉํด๋ณด๋๋ก ํ์.
์๋ ๊ฒฝ๋ก์์ WidgetExtension์ ์ถ๊ฐํด ์ฃผ๋๋ก ํ์.
File > Target > ios
์ํ๋ WidgetKit ์ด๋ฆ์ ์ถ๊ฐํด์ฃผ๋ฉด ๋๋๋ฐ, ์ฌ๊ธฐ์ ์ค์ํ ๋ถ๋ถ์ด ๋ฐ๋ก "include Configuration App Intent" ๋ถ๋ถ์ด๋ค.
์์์ ์ค๋ช ํ ๊ฒ์ฒ๋ผ WidgetKit์ StaticConfiguration๊ณผ IntentConfiguration ์ด๋ ๊ฒ ๋ ๊ฐ์ง ์ค์ ์ด ์๋ค๊ณ ํ์๋ค.
์ด ๋ถ๋ถ์ ์ฒดํฌํ๊ฒ ๋๋ฉด IntentConfiguration์ผ๋ก ์์ฑ๋๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์, ์ฐ์ ์ฒดํฌ๋ฅผ ํด์ ํ๊ณ StaticConfiguration์ผ๋ก ์์ฑํด ์ฃผ๋๋ก ํ์.
์ด์ ๊ฐ๋จํ ์์ ๊ฐ ์์ฑ๋ ํ์ผ์ด ์์ฑ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
ํ๋ก์ ํธ๋ฅผ ๋น๋ํ ํ์ ์์ ฏ์ ์ถ๊ฐํด๋ณด๋ฉด, ์์ ฏ์ ์ ํํ ์ ์๋๋ก ๋ํ๋ด๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
![]() |
![]() |
![]() |
๊ฐ๋จํ๊ฒ UI ์ ์ธ ๋ถ๋ถ์ ๊ฐ๋ณ๊ฒ ์ดํด๋ณด๋๋ก ํ์.
๋จผ์ ์ฌ์ด์ฆ์ด๋ค. WidgetKit์ ์ฌ์ด์ฆ๋ ์ด 4๊ฐ์ง ํ์ ์ ์ฌ์ฉํ๋๋ก ๋์ด ์์ผ๋ฉฐ, ์ํ๋ ์ฌ์ด์ฆ๋ง์ ์์๋ก ์ง์ ํ ์๋ ์๋ค.
.systemSmall
.systemMedium
.systemLarge
.systemExtraLarge (only iPad)
์ํ๋ ์ฌ์ด์ฆ๋ง์ ์ง์ ํ๊ณ ์ถ๋ค๋ฉด supportedFamilies ํ๋กํผํฐ๋ฅผ ์ฌ์ฉํด ๋ฐฐ์ด๋ก enum ํ์ ์ ์ฌ์ด์ฆ๋ฅผ ์ถ๊ฐํด ์ฃผ๋ฉด ๋๋ค.
struct StaticWidgetKit: Widget {
let kind: String = "StaticWidgetKit"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
StaticWidgetKitEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies([.systemSmall, .systemLarge])
}
}
์ฌ์ด์ฆ๋ณ๋ก UI๋ฅผ ๋ถ๊ธฐํ๊ณ ์ถ๋ค๋ฉด ํ๊ฒฝ๋ณ์๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
struct MyWidgetEntryView : View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .systemSmall:
SmallWidgetView()
case .systemMedium:
MediumWidgetView()
case .systemLarge:
LargeWidgetView()
case .systemExtraLarge:
ExtraLargeWidgetView()
@unknown default:
SmallWidgetView()
}
}
}
์์ ฏ์ ์ ํํ ๋์ ์๋จ์ ํ์ดํ๊ณผ ์ค๋ช ์ด ๋ ธ์ถ๋๋ ๊ฒ์ ์ ์ ์๋๋ฐ, ์ด ๋ถ๋ถ์ ๋ณ๊ฒฝํ๊ณ ์ถ๋ค๋ฉด ๊ฐ๊ฐ configurationDisplayName, description ํ๋กํผํฐ์ ํ ์คํธ๋ฅผ ๋ณ๊ฒฝํด์ฃผ๋ฉด ๋๋ค.
TimelineProvider ๊ฐ์ฒด์ ๋ํด์ ํ์ธํด ๋ณด๋๋ก ํ์.
TimelineEntry ๋ถ๋ถ์ emoji ๋์ ํ ์คํธ๋ฅผ ๋ฐ์์ค๋๋ก ์์ ํ๊ณ , TimelineProvider ๋ฉ์๋๋ค์ ์ํ๋ฅผ ๊ฐ์ ธ์ค๋๋ก ํ์.
struct SimpleEntry: TimelineEntry {
let date: Date
let text: String
}
placeholder, getSnapshot, getTimeline ๋ฉ์๋ ์์ TimelineEntry ๋ฅผ ํธ์ถํ ๋์ ๊ฐ ์ํ๋ฅผ ํ ์คํธ๋ก ๋ฃ์ด์ฃผ๋๋ก ํ์.
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), text: "placeholder")
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), text: "getSnapshot")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, text: "getTimeline")
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
์ด์ ๋ค์ ๋น๋ํ ํ์ ์ํ๋ฅผ ํ์ธํด๋ณด๋ฉด, ์์ ฏ์ ์ถ๊ฐํ๋ ค๊ณ ํ ๋์ getSnapshot ๋ฉ์๋์ TimelineEntry๋ฅผ ์ฌ์ฉํ๊ณ , ์์ ฏ์ด ์ถ๊ฐ๋ ํ์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๊ณ ๋๋ฉด getTimeline ๋ฉ์๋๊ฐ ํธ์ถ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
placeholder ๋ฉ์๋๊ฐ ํธ์ถ๋์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์๋ฐ, ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋๋ฐ ์ด๊ธฐ์ ๋ก๋ฉ์ด ๋ฐ์ํ๋ ๊ฒฝ์ฐ์๋ง ๋ํ๋๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ผ๋ก๋ ๋น ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ ๋ณด์ฌ์ฃผ๋ ์ฉ๋๋ก ์ฌ์ฉ๋๋ค.
![]() |
![]() |
getTimeline ๋ฉ์๋๋ฅผ ์์ ํด์ ์๊ฐ์ ๋ฐ๋ฅธ ์ ๋ฐ์ดํธ ์์ ์ ์กฐ์ ํด๋ณด๋๋ก ํ์.
๋ฐ๋ณต๋ฌธ์ ์ฌ์ฉํด์ 5๊ฐ์ Entry ๊ฐ์ฒด๋ฅผ 3์ด ๊ฐ๊ฒฉ์ผ๋ก ์ ๋ฐ์ดํธ ํ๋๋ก ๋ณ๊ฒฝํ๋ ์ฝ๋์ด๋ค.
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
for i in 0..<5 {
let entryDate = Calendar.current.date(byAdding: .second, value: i * 3, to: currentDate)!
let entry = SimpleEntry(date: entryDate, text: "getTimeline")
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
3์ด ๋ง๋ค | 2์ด ๋ง๋ค |
---|---|
![]() |
![]() |
getTimeline ๋ฉ์๋์์์ Entry ๊ฐ์ฒด์ date ํ๋กํผํฐ์ ์ค์ ์ ๋ฐ๋ผ ์๊ฐ์ ์ํ๋ ์ฃผ๊ธฐ๋ก ์์ฑํด ์ค ์ ์๋ค.
policy์ ๋ํ ๋ถ๋ถ์ ์ดํด๋ณด๋๋ก ํ๊ฒ ๋ค.
๋ฆฌ๋ก๋ ์๊ธฐ๋ฅผ ์ค์ ํ๋ ๋ถ๋ถ์ธ๋ฐ, .atEnd, .after(Date), .never 3๊ฐ์ง์ ๊ธฐ์ค์ ์ฌ์ฉํ ์ ์๋ค.
ํ์๋ผ์ธ์ ๋ํ ๊ธฐ์ค์ ๋ํ๋ด๋ ํ๋ก์ฐ์ด๋ค.
atEnd
atEnd๋ ๋ง์ง๋ง ํ์๋ผ์ธ ์ด ํ์ ์๋ก์ด ํ์๋ผ์ธ์ ์์ฒญํ๊ฒ ๋๋ค.
๋ง์ฝ์ [now, 1hr, 2hr] ์ด๋ ๊ฒ ํ์๋ผ์ธ์ ์์ฑ ํ์๋ค๋ฉด, ์๋ก์ด ํ์๋ผ์ธ์ ์์ฒญํ์ฌ [2hr, 3hr, 4hr]์ ํ์๋ผ์ธ์ ๋ค์ ์์ฑํ์ฌ ์ฃผ๊ธฐ์ ์ผ๋ก ์ ๋ฐ์ดํธ ํ ์ ์๊ฒ ๋๋ค.
after(Date)
after๋ ํน์ ์์ ์ด ํ์ ์๋ก์ด ํ์๋ผ์ธ์ ์์ฒญํ๊ฒ ๋๋ ๋ฐฉ์์ด๋ค.
[now, 1hr, 2hr] ํ์๋ผ์ธ์ ์์ฑํ๊ณ after๋ฅผ 1์๊ฐ ํ๋ก ์ค์ ํ๊ฒ ๋๋ฉด, ํ์๋ผ์ธ์ ์ง๊ธ์ผ๋ก ๋ถํฐ 1์๊ฐ ๋ค์ ๋ค์ ์์ฑํ๋ค๋ ์๋ฏธ์ด๋ค.
never
never๋ ๋ ์ด์ ์๋ก์ด ํ์๋ผ์ธ์ ์์ฒญํ๊ณ ์ถ์ง ์์ ๋ ์ฌ์ฉํ๋ฉด ๋๋ฉฐ, never ์ฌ์ฉ์ ๋ ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธ ํ์ง ์๋๋ค.
policy ๊ธฐ์ค์ ๋ง๋ค๊ณ ์ ํ๋ ๊ธฐ๋ฅ์ ๋ง๊ฒ ๊ธฐ์ค์ ํ ์คํธ ํด๋ณด๋ฉด์ ์ ๋นํ ํ์ ์ ์ฌ์ฉํด์ ์์ฑ๋์ด์ผ ํ๊ณ WidgetKit์ ์์ฑ๋ ํ์๋ผ์ธ์ ์ํด ์ ํํ ์ ๋ฐ์ดํธ๊ฐ ๋์ง ์์ ์ ์๊ธฐ ๋๋ฌธ์ ์ด ๋ถ๋ถ๋ ๋ฐ๋์ ๊ณ ๋ คํ๊ณ ์์ด์ผ ํ๋ค.
์์ ฏ์ ์ฌ๋ฌ ์๋น์ค์ ์ฑ๋ค์ด ์ง์ํ์ฌ ์ฌ์ฉ๋๋ค ๋ณด๋ ๋ชจ๋ ์์ ฏ์ ์ ๋ฐ์ดํธ ์๊ธฐ๊ฐ ์์ฃผ ํธ์ถ๋๋ค๋ฉด ๋๋ฐ์ด์ค๊ฐ ๊ณผ๋ถํ ๊ฑธ๋ฆด ์๋ ์๊ธฐ ๋๋ฌธ์ ์ ํ์ ๋ฐฐํฐ๋ฆฌ ์๋ช ์ ์ต์ ํํ๊ธฐ ์ํด ๋๋ต 15๋ถ ๋จ์๋ก ๊ถ์ฅํ๊ณ ์๊ณ ์ ๋์ ์ธ ๊ท์น์ ์๋๋ผ๊ณ ํ๋ค.
์ง๊ธ์ ํ ์คํธ๋ฅผ ํ๊ธฐ ์ํด์ ์ ๋ฐ์ดํธ๋ฅผ ์ด๋จ์๋ก ํด๋ ๋ฌธ์ ๊ฐ ์์ง๋ง ์ค์ ์ด์๋๋ ์ฑ์ ์ ๋ฐ์ดํธ ์์ ์ ๊ณต๊ฒฉ์ ์ผ๋ก ์ฌ์ฉํ์ง ์๋๋ก ๊ณ ๋ คํด์ ์ค๊ณํด์ผ ๋ฌธ์ ๊ฐ ์๋ค.
์ด์ด์ IntentConfiguration ์์ ฏ์ ์ฌ์ฉํ ๋์ ์ถ๊ฐ๋ ๋ถ๋ถ์ ์ดํด๋ณด๋๋ก ํ์.
WidetKit ์ถ๊ฐ์ "include Configuration App Intent" ๋ฅผ ์ฒดํฌํด์ ์์ฑํด์ฃผ๋ฉด ๋๋ค.
์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด TimelineEntry ๊ฐ์ฒด์ ConfigurationAppIntent ๊ฐ์ฒด๋ฅผ ํ์๋ก ๋ฐ์์ค๋๋ก ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ด ๋ถ๋ถ์ด StaticConfiguration ๊ณผ์ ๋ค๋ฅธ ์ ์ด๋ค.
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationAppIntent
}
ConfigurationAppIntent ์ฝ๋๋ ์์ฑ๋์ด ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
extension ConfigurationAppIntent {
fileprivate static var smiley: ConfigurationAppIntent {
let intent = ConfigurationAppIntent()
intent.favoriteEmoji = "๐"
return intent
}
fileprivate static var starEyes: ConfigurationAppIntent {
let intent = ConfigurationAppIntent()
intent.favoriteEmoji = "๐คฉ"
return intent
}
}
AppIntent ๋ผ๋ ํ์ผ์์ ConfigurationAppIntent์ ๋ํ ์ฝ๋๊ฐ ์ฌ๊ธฐ์์ ์์ฑ๋์ด ์๋ค.
import WidgetKit
import AppIntents
struct ConfigurationAppIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Configuration"
static var description = IntentDescription("This is an example widget.")
// An example configurable parameter.
@Parameter(title: "Favorite Emoji", default: "๐")
var favoriteEmoji: String
}
Intent | Static |
---|---|
![]() |
![]() |
๊ฒฐ๊ตญ WidgetConfigurationIntent ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์์ ฏ ํธ์ง์ ๋ง๋ค์ด ์ค ์ ์๋ค.
์ ํ์ฐฝ์ ๋ง๋ค์ด ์์ ฏ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ปฌ๋ฌ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋๋ก ํด๋ณด์.
๋จผ์ WidgetConfigurationIntent๋ฅผ ์ฌ์ฉํด์ ํธ์ง์ฐฝ์ ์ ํ ๊ฐ๋ฅํ๋๋ก ์์ ํด ์ฃผ์.
struct SelectColorAppIntent : WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Select Color"
@Parameter(title: "color",default: .redType)
var type : SelectColorType
}
SelectColorType์ ์์ฑํด ์ฃผ๋๋ก ํ์.
enum SelectColorType: String, AppEnum {
case redType, blueType, orangeType
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Color Type"
static var caseDisplayRepresentations: [SelectColorType : DisplayRepresentation] = [
.redType: "Red",
.blueType:"Blue",
.orangeType: "Oragnge",
]
}
์์ฑํ WidgetConfigurationIntent ๊ฐ์ฒด๋ฅผ ํ์ฅํด ์ฃผ๊ณ , ํด๋นํ๋ ํ์ ์ ์ปฌ๋ฌ๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ ์ปฌ๋ฌ๋ฅผ ๋ง๋ค์ด์ฃผ์.
struct IntentWidgetKit: Widget {
let kind: String = "IntentWidgetKit"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind, intent: SelectColorAppIntent.self, provider: Provider()) { entry in
IntentWidgetKitEntryView(entry: entry)
.containerBackground(entry.configuration.type == .redType ? .red : entry.configuration.type == .blueType ? .blue : .orange, for: .widget)
}
}
}
extension SelectColorAppIntent {
fileprivate static var redType : SelectColorAppIntent {
let intent = SelectColorAppIntent()
intent.type = .redType
return intent
}
fileprivate static var blueType : SelectColorAppIntent {
let intent = SelectColorAppIntent()
intent.type = .blueType
return intent
}
fileprivate static var orangeType : SelectColorAppIntent {
let intent = SelectColorAppIntent()
intent.type = .orangeType
return intent
}
}
๊ฐ๋จํ๊ฒ ๊ตฌ์ฑํด ๋ดค๋๋ฐ, ์ ํ ์์ ฏ ์ธ์๋ ํ ์คํธ ์ ๋ ฅ, ์ค์์น ๋ฑ์ ๋ค์ํ ๊ตฌ์ฑ์ ํธ์ง๊ธฐ๋ฅ์ผ๋ก ์ฌ์ฉํ ์ ์์ผ๋ ์ถ๊ฐ์ ์ผ๋ก ํด๋น ๋ถ๋ถ์ ๋ณ๋๋ก ๊ธ์ ์์ฑํด ๋ณด๋๋ก ํ๊ฒ ๋ค.
![]() |
![]() |
์ด์ด์ ์์ ฏ์ ํด๋ฆญํ์ ๋์ ์ฒ๋ฆฌํ ์ ์๋ ๋ฅ๋งํฌ๋ฅผ ์ถ๊ฐํด ์ฃผ๋๋ก ํ์.
๋จผ์ WidgetKit์ View ๊ฐ์ฒด์์ ์ํ๋ ํฐ์น ํฌ์ธํธ์ widgetUrl์ ์ฌ์ฉํด ์ฃผ๋ฉด ๋๋ค.
URL ํ์ ์ ์ฌ์ฉํ์ฌ ์ํ๋ ์คํด์ ๋ง๋ค์ด ์ฃผ๋ฉด ๋๋ค.
struct IntentWidgetKitEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text(entry.date, style: .time)
}.widgetURL(URL(string: "widgetSample://test.com"))
}
}
์์ฑํ ์คํด์ ์์ ํ๊ธฐ ์ํด WidowGroup ํ์ ์์ ฏ์ onOpenURL์ ์ฌ์ฉํด ์คํด ์ ๋ณด๋ฅผ ์์ ๋ฐ์ ์ฒ๋ฆฌํด์ฃผ๋ฉด ๋๋ค.
import SwiftUI
@main
struct WidgetKitSampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: { url in
print(url)
})
}
}
}
์์ ฏ์ ์ผ๋ถ ๋๋ ์ ์ฒด ํ์๋ผ์ธ์ ๋ค์ ๋ก๋ํ๊ฑฐ๋ ๊ตฌ์ฑ์ ๊ฐ์ ธ์ค๋ ๋ฐ ์ฌ์ฉ๋๋ ๊ธฐ๋ฅ์ผ๋ก ์์ ฏ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ๋์ ์ผ๋ก ์ ๋ฐ์ดํธ ๋๋ ์ํ๋ฅผ ํ์ธํ ์ ์๋ ๊ธฐ๋ฅ์ด๋ค.
ํด๋น ๊ธฐ๋ฅ๋ iOS 14 ์ด์์์๋ง ๋์ํ๋ WidgetKit์ ์ผ๋ถ ๊ธฐ๋ฅ์ด๋ค.
์๋ฅผ ๋ค์ด ์์ ฏ์ ์ฌ์ฉ์์ ์ฃผ์ ์์ต๋ฅ ์ ๋ณด์ฌ์ฃผ๊ณ ์๋ค๊ณ ๊ฐ์ ํด๋ณด์.
ํด๋น ์ฌ์ฉ์์ ์ธ์ฆ ์ฌ๋ถ์ ๋ฐ๋ผ ์์ ฏ์ ์
๋ฐ์ดํธ ํด์ฃผ์ด์ผ ํ๋๋ฐ, ์ฌ์ฉ์์ ๊ณ์ ์ด ๋ก๊ทธ์์ ๋๋๋ผ๋ ํ์๋ผ์ธ์ด ์ฆ๊ฐ ์
๋ฐ์ดํธ ๋๋ค๋ ๋ณด์ฅ์ด ์๊ธฐ ๋๋ฌธ์ ๋ณ๋๋ก ๋ก๊ทธ์์ ์ฒ๋ฆฌ์ WidgetCenter๋ฅผ ์ฌ์ฉํด ๋ฐ์ดํฐ๋ ์ํ๋ฅผ ์ฆ์ ์
๋ฐ์ดํธ ์์ผ์ค ์ ์๊ฒ ๋๋ค.
ํ์ฌ ์์ ฏ์ ์ํ๋ฅผ ํ์ธํ ์ ์๋ ๊ธฐ๋ฅ์ด๋ค.
WidgetCenter.shared.getCurrentConfigurations { result in
switch result {
case .success(let widgetInfos):
for widgetInfo in widgetInfos {
print("Widget kind: \(widgetInfo.kind)")
print("Widget family: \(widgetInfo.family)")
}
case .failure(let error):
print("Error fetching widget configurations: \(error.localizedDescription)")
}
}
WidgetKit์์ ์ฌ์ฉ ์ค์ธ ํน์ ์์ ฏ์ ํ์๋ผ์ธ์ ๋ค์ ์์ฑํ ๋์ ์ฌ์ฉํ ์ ์๋ค.
WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")
WidgetKit์์ ์ฌ์ฉ ์ค์ธ kind์ ์ผ์นํ์ฌ์ผ ํ๋๋ฐ, kind๋ WidgetKit์์ ์ค์ ํ ์ ์๋ค.
ํน์ ์์ ฏ์ด ์๋ ์ ์ฒด WidgetKit์ ํ์๋ผ์ธ์ ๋ค์ ์์ฑํด ์ค ๋์ ์ฌ์ฉํ ์ ์๋ค.
WidgetCenter.shared.reloadAllTimelines()
์ด๋ฒ์๋ ์ฑ์ ์ซ์๋ฅผ ์ฆ๊ฐ์์ผ UserDefaults๋ฅผ ์ฌ์ฉํด ์ฆ๊ฐํ ๊ฐ์ ์ ์ฅํ๊ณ WidgetKit์์ ํด๋น ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ์ ๋ฐ์ดํธ ์์ ์ ์ฆ๊ฐ๋ ์นด์ดํธ ๊ฐ์ ๋ณด์ฌ์ค ์ ์๋๋ก ํด๋ณด์.
![]() |
![]() |
WidgetKit์์ ๊ฐ์ ธ์ค๋ UserDefaults ๊ฐ์ด ๊ณต์ ๋์ง ๋ชปํ๊ณ ์๋ ๊ฒ์ ์ ์ ์๋ค.
์ด์ ๋ WidgetKit์ ํ๊ฒฝ์ด ์ฑ๊ณผ๋ ๋ ๋ฆฝ์ ์ผ๋ก ์๋๋๊ธฐ ๋๋ฌธ์ ํ๊ฒฝ์ด ์์ ํ ๋ถ๋ฆฌ๋์ด ์๋ ์ํ์ด๋ค. ์ฆ ๋์ผ ํ๊ฒฝ์ ์์นํ ์ฑ์ด ์๋ ๊ฒ์ด๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ๊ณต์ ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํด ๋ฐ์ดํฐ๊ฐ ๊ณต์ ๋ ์ ์๋๋ก ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํด ์ฃผ์ด์ผ ํ๋ค.
AppGroup์ ์ถ๊ฐํด UserDefaultsํ๊ฒฝ์ ๊ณต์ ์์ผ ์ฃผ๋๋ก ํ์.
TARGETS์ ํด๋ฆญํด์ Capablility๋ฅผ ์ถ๊ฐํด ์ฃผ๋๋ก ํ์.
App Groups๋ฅผ ์ถ๊ฐํด ์ฃผ๋ฉด ๋๋ค.
App Groups๊ฐ ์ถ๊ฐ๋ ๊ฒ์ด ๋ณด์ด๋๋ฐ, Apple Developer์์ ๋์ผ ๊ณ์ ์ผ๋ก ์ฌ์ฉ์ค์ธ App Groups ๋ชฉ๋ก์ด ๋ชจ๋ ๋ณด์ฌ์ง๋๋ฐ, ์๋กญ๊ฒ ๊ทธ๋ฃน์ ์์ฑํ๊ณ ์ถ๋ค๋ฉด "+"๋ฅผ ํด๋ฆญํด ์ฃผ๋๋ก ํ์.
ํด๋น ํ๋ก์ ํธ์์ ์ฌ์ฉํ๊ณ ์ถ์ ๊ทธ๋ฃน์ ์ด๋ฆ์ ์์ ๋กญ๊ฒ ์ง์ ํด ์ฃผ๋ฉด๋๋ค.
์๋กญ๊ฒ ์ถ๊ฐ๋ App Group์ ์ ํํด ์ฒดํฌํด์ฃผ์.
ํ๊ฒฝ์ ๊ณต์ ํ๊ณ ์ถ์ Widget์ ํ๊ฒ์ ๋ณ๊ฒฝํ์ฌ ๋์ผํ๊ฒ App Groups๋ฅผ ์ถ๊ฐํด ์ค๋ค, ์์ ์ฒดํฌํ ๊ทธ๋ฃน์ ๋์ผํ ๊ทธ๋ฃน์ ์ฌ๊ธฐ์๋ ์ฒดํฌํด ์ฃผ๋๋ก ํ์.
์ด์ UserDefaults ์ธ์คํด์ค์ suiteName์ ์ถ๊ฐํ App Group์ ์ฌ์ฉํ๋ฉด ๋๋ค.
UserDefaults(suiteName: "group.tyger.widgetSample")
์ด์ ์ ์์ ์ผ๋ก ๋ก์ปฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๋ ๋ฐ์ดํฐ๊ฐ ๊ณต์ ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
![]() |
![]() |
iOS์ WidgetKit์ ๋ํด์ ๊ธ์ ์์ฑํ๋๋ฐ, ํ ๋ฒ์ ๋ค๋ฃฐ ๋ด์ฉ์ด ๋ง์์ ๋ํ ์ผํ๊ฒ ๋ค๋ค๋ด์ผ ํ ๋ด์ฉ์ด๋ ์ฌ์ฉ ๋ฐฉ๋ฒ๋ค์ ์ ๋ถ ์์ฑํ์ง๋ ๋ชปํ์๋ค.
์ถ๊ฐ์ ์ธ ๋ถ๋ถ์ ๋ํด์๋ ๋ณ๋๋ก ๊ธ์ ์์ฑํ ์์ ์ด๊ณ , Android์์ ์ฌ์ฉํ๋ ์์ ฏ์ ๋ํ ๋ด์ฉ๊ณผ Flutter์์ iOS, Android์ ์์ ฏ์ ์ฐ๊ฒฐํด ์ฌ์ฉํ ์ ์๋์ง์ ๋ํด์๋ ์์ฑํด ๋ณด๋๋ก ํ๊ฒ ๋ค.
๊ธด ๊ธ ์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค !