๐ŸŽ [Swift] WidgetKit ์ด๋ž€ ?

Tygerยท2024๋…„ 6์›” 23์ผ
1

Swift with iOS

๋ชฉ๋ก ๋ณด๊ธฐ
1/3
post-thumbnail

๐ŸŽ WidgetKit ์ด๋ž€ ?

WidgetKit | Apple Developer

๐Ÿ”’ ์ž ๊ธˆํ™”๋ฉด ์œ„์ ฏ ๋งŒ๋“ค๊ธฐ - WidgetKit
๐ŸŽธ Live Activity ์‚ฌ์šฉํ•ด ๋ณด๊ธฐ

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” WidgetKit์— ๋Œ€ํ•ด์„œ ๋‹ค๋ค„๋ณผ ์˜ˆ์ •์ด๋‹ค.

์ฃผ๋กœ Flutter์— ๊ด€ํ•œ ๊ธ€๋งŒ ์ž‘์„ฑํ•˜๋Š” ํŽธ์ธ๋ฐ, ๋‹ค๋ฅธ ์–ธ์–ด๋‚˜ ํ”„๋ ˆ์ž„์›Œํฌ์— ๋Œ€ํ•ด์„œ ์ž‘์„ฑํ•ด ๋†“์ง€ ์•Š๋‹ค๋ณด๋‹ˆ ๊ฐ€๋” ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•  ๋•Œ๋งˆ๋‹ค ์žŠ์–ด๋ฒ„๋ ค์„œ ์ •๋ฆฌํ•˜๋Š” ์ฐจ์›์—์„œ ์ž‘์„ฑ์„ ํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

WidgetKit

WidgetKit์ด๋ž€ iOS 14 ๋ฒ„์ „๋ถ€ํ„ฐ ๋„์ž…๋œ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ์‚ฌ์šฉ์ž๊ฐ€ ํ™ˆ ํ™”๋ฉด ๋ฐ ์ž ๊ธˆํ™”๋ฉด์—์„œ ์†์‰ฝ๊ฒŒ ์ •๋ณด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์œ„์ ฏ์„ ๊ฐœ๋ฐœํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

iOS 14 ์ดํ•˜๋ฅผ ํƒ€๊ฒŸ ํ•œ๋‹ค๋ฉด ๋‹น์—ฐํžˆ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•ด ์ฃผ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ถ€๋ถ„์„ ๋†“์น˜์ง€ ๋ง์•„์•ผ ํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” iOS 14 ์ด์ƒ๋งŒ ํƒ€๊ฒŸํ•˜๊ธฐ์— ๊ณ ๋ คํ•˜์ง€ ์•Š์„ ์˜ˆ์ •์ด๋‹ค.

Static ? Intent ?

WidgetKit์—์„œ ์œ„์ ฏ์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

Static Configuration๊ณผ Intent Configuration์ด๋‹ค.

Static Configuration

๋จผ์ € Static์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด์ž๋ฉด, ์˜๋ฏธ ๊ทธ๋Œ€๋กœ ๊ณ ์ •๋œ ์œ„์ ฏ์ด๋‹ค.

์œ„์ ฏ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ณ ์ •๋˜์–ด์„œ ์ •์ ์ด๊ฑฐ๋‚˜ ์ฃผ๊ธฐ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ๋งŒ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์œ„์ ฏ ์ข…๋ฅ˜์ด๋‹ค.

Intent Configuration

๊ทธ๋ ‡๋‹ค๋ฉด Intent๋Š” ๋‹น์—ฐํžˆ ๋ฐ˜๋Œ€ ๊ฐœ๋…์ผ ๊ฒƒ์ด๋‹ค.

์œ„์ ฏ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ณ ์ •์ ์ด์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์‚ฌ์šฉ์ž ๊ฐœ์ธํ™”๊ฐ€ ๊ฐ€๋Šฅํ•œ ์œ„์ ฏ์„ ์ ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

๊ธ€๋กœ ๋ณด๋ฉด ์ดํ•ด๊ฐ€ ์•ˆ๋  ์ˆ˜ ์žˆ์ง€๋งŒ iOS์—์„œ๋Š” ์œ„์ ฏ์˜ ํŽธ์ง‘ ๊ธฐ๋Šฅ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์™€ ์—†๋Š” ๊ฒฝ์šฐ ๋”ฑ ๋‘ ๊ฐ€์ง€๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ ์ฐจ์ด๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

์ฆ‰ Static Configuration์€ ์• ํ”Œ ๊ธฐ๋ณธ ์•ฑ ์ค‘ ๋‚ ์”จ, ์บ˜๋ฆฐ๋”, ๋ฐฐํ„ฐ๋ฆฌ ์œ„์ ฏ๊ณผ ๊ฐ™์ด ์ •ํ•ด๋†“์€ ๋ฐ์ดํ„ฐ๋งŒ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ๋ณด์—ฌ์ง€๊ฒŒ ๋˜๊ณ  ๋ฐ˜๋ฉด ๋ฏธ๋ฆฌ์•Œ๋ฆผ, ๋‹จ์ถ•์–ด, ํŒŸ์บ์ŠคํŠธ ์ฒ˜๋Ÿผ ์œ„์ ฏ์—์„œ ๋ณด์—ฌ์งˆ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์„ ํƒํ•˜์—ฌ ์œ„์ ฏ์„ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด Intent Configuration์ธ ๊ฒƒ์ด๋‹ค.

๋‹น์—ฐํžˆ Intent Configuration์ด ๊ตฌ์กฐ๊ฐ€ ๋” ๋ณต์žกํ•˜๊ณ  ๊ตฌํ˜„ํ•˜๊ธฐ๊ฐ€ ์ƒ๋Œ€์ ์œผ๋กœ ๋ณต์žกํ•˜๊ฒŒ ๋œ๋‹ค.

Components

WidgetKit์„ ์‚ฌ์šฉํ•˜๊ธฐ ์•ž์„œ ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ์— ๋Œ€ํ•ด์„œ ์šฐ์„  ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

TimelineProvider, Entry, Timeline ์ด๋ ‡๊ฒŒ 3 ๊ฐ€์ง€์˜ ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์žˆ๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š”๋ฐ ํ•ต์‹ฌ์ ์ธ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์˜์—ญ์ด๋‹ค.

TimelineProvider

์œ„์ ฏ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ”„๋กœํ† ์ฝœ๋กœ, ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ์–ธ์ œ ํ‘œ์‹œํ• ์ง€๋ฅผ ์ •์˜ํ•˜๋Š” ์š”์†Œ์ด๋‹ค.

placeholder
์œ„์ ฏ์˜ ์ดˆ๊ธฐ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์‹œ ์š”์ฒญ์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ๋ณด์—ฌ์ค„ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•œ๋‹ค.

getSnapshot
๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋˜๋Š” ๋น ๋ฅธ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•ด ํ˜„์žฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์œ„์ ฏ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์„ค์ •์—์„œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์‹œ์— ํ˜ธ์ถœ๋˜๋Š” ๋ฉ”์„œ๋“œ์ด๋‹ค.

getTimeline
์ผ์ • ๊ฐ„๊ฒฉ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ๋ฅผ ํฌํ•จํ•œ ํƒ€์ž„๋ผ์ธ์„ ์ œ๊ณตํ•˜๋ฉฐ, ์—ฌ๋Ÿฌ TimelineEntry๋ฅผ ํฌํ•จํ•œ Timeline ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ์œ„์ ฏ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

Entry

Entry๋Š” TimelineEntry ํ”„๋กœํ† ์ฝœ์„ ์ค€์ˆ˜ํ•˜๋Š” ๊ฐ์ฒด๋กœ, ์œ„์ ฏ์— ํ‘œ์‹œ๋  ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ ์ƒ์„ฑ๋œ ๊ฐ Entry๋Š” ํŠน์ • ์‹œ๊ฐ„์— ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ• ์ง€๋ฅผ ์ •์˜ํ•˜๊ฒŒ ๋œ๋‹ค.

date ํ”„๋กœํผํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•ด ์‹œ๊ฐ„์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฐ์ดํ„ฐ ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋ณด์—ฌ์ค„ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

Timeline

์—ฌ๋Ÿฌ ๊ฐœ์˜ Entry๋กœ ๊ตฌ์„ฑ๋œ ๊ฐ์ฒด๋กœ, ํŠน์ • ์‹œ๊ฐ„์— ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ• ์ง€๋ฅผ ์ •์˜ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

entries
TimelineEntry ๊ฐ์ฒด์˜ ๋ฐฐ์—ด๋กœ ๊ฐ Entry๋Š” ํŠน์ • ์‹œ๊ฐ„์— ํ‘œ์‹œ๋  ๋ฐ์ดํ„ฐ์™€ ์‹œ๊ฐ„์„ ํฌํ•จํ•œ๋‹ค.

reloadPolicy
๋ฆฌ๋กœ๋“œ ๋˜์–ด์•ผํ•˜๋Š” ์‹œ์ ์„ ์ •์˜ํ•˜๋ฉฐ .atEnd, .after, .never๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Add WidgetKit

์ด์ œ XCode์— ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•œ ๋’ค์— ๋ณธ๊ฒฉ์ ์œผ๋กœ WidgetKit์„ ์‚ฌ์šฉํ•ด๋ณด๋„๋ก ํ•˜์ž.

์•„๋ž˜ ๊ฒฝ๋กœ์—์„œ WidgetExtension์„ ์ถ”๊ฐ€ํ•ด ์ฃผ๋„๋ก ํ•˜์ž.

File > Target > ios

์›ํ•˜๋Š” WidgetKit ์ด๋ฆ„์„ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋˜๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด ๋ฐ”๋กœ "include Configuration App Intent" ๋ถ€๋ถ„์ด๋‹ค.

์œ„์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ WidgetKit์€ StaticConfiguration๊ณผ IntentConfiguration ์ด๋ ‡๊ฒŒ ๋‘ ๊ฐ€์ง€ ์„ค์ •์ด ์žˆ๋‹ค๊ณ  ํ–ˆ์—ˆ๋‹ค.

์ด ๋ถ€๋ถ„์„ ์ฒดํฌํ•˜๊ฒŒ ๋˜๋ฉด IntentConfiguration์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์šฐ์„  ์ฒดํฌ๋ฅผ ํ•ด์ œํ•˜๊ณ  StaticConfiguration์œผ๋กœ ์ƒ์„ฑํ•ด ์ฃผ๋„๋ก ํ•˜์ž.

์ด์ œ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๊ฐ€ ์ž‘์„ฑ๋œ ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ”„๋กœ์ ํŠธ๋ฅผ ๋นŒ๋“œํ•œ ํ›„์— ์œ„์ ฏ์„ ์ถ”๊ฐ€ํ•ด๋ณด๋ฉด, ์œ„์ ฏ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‚˜ํƒ€๋‚ด๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

UI

๊ฐ„๋‹จํ•˜๊ฒŒ UI ์ ์ธ ๋ถ€๋ถ„์„ ๊ฐ€๋ณ๊ฒŒ ์‚ดํŽด๋ณด๋„๋ก ํ•˜์ž.

WidgetFamily

๋จผ์ € ์‚ฌ์ด์ฆˆ์ด๋‹ค. 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()
        }
    }
}

Display

์œ„์ ฏ์„ ์„ ํƒํ•  ๋•Œ์— ์ƒ๋‹จ์— ํƒ€์ดํ‹€๊ณผ ์„ค๋ช…์ด ๋…ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด ๋ถ€๋ถ„์„ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๊ฐ๊ฐ configurationDisplayName, description ํ”„๋กœํผํ‹ฐ์— ํ…์ŠคํŠธ๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

TimelineProvider

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

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๋ถ„ ๋‹จ์œ„๋กœ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ๊ณ  ์ ˆ๋Œ€์ ์ธ ๊ทœ์น™์€ ์•„๋‹ˆ๋ผ๊ณ  ํ•œ๋‹ค.

์ง€๊ธˆ์€ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์—…๋ฐ์ดํŠธ๋ฅผ ์ดˆ๋‹จ์œ„๋กœ ํ•ด๋„ ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ ์‹ค์ œ ์šด์˜๋˜๋Š” ์•ฑ์€ ์—…๋ฐ์ดํŠธ ์‹œ์ ์„ ๊ณต๊ฒฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ๊ณ ๋ คํ•ด์„œ ์„ค๊ณ„ํ•ด์•ผ ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค.

Intent Configuration

์ด์–ด์„œ 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
    }
}

๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌ์„ฑํ•ด ๋ดค๋Š”๋ฐ, ์„ ํƒ ์œ„์ ฏ ์™ธ์—๋„ ํ…์ŠคํŠธ ์ž…๋ ฅ, ์Šค์œ„์น˜ ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ๊ตฌ์„ฑ์„ ํŽธ์ง‘๊ธฐ๋Šฅ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ถ”๊ฐ€์ ์œผ๋กœ ํ•ด๋‹น ๋ถ€๋ถ„์€ ๋ณ„๋„๋กœ ๊ธ€์„ ์ž‘์„ฑํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

WidgetURL

์ด์–ด์„œ ์œ„์ ฏ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋”ฅ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ๋„๋ก ํ•˜์ž.

๋จผ์ € 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)
                })
        }
    }
}

WidgetCenter

์œ„์ ฏ์˜ ์ผ๋ถ€ ๋˜๋Š” ์ „์ฒด ํƒ€์ž„๋ผ์ธ์„ ๋‹ค์‹œ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ๊ตฌ์„ฑ์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ์œ„์ ฏ์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋™์ ์œผ๋กœ ์—…๋ฐ์ดํŠธ ๋˜๋Š” ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

ํ•ด๋‹น ๊ธฐ๋Šฅ๋„ iOS 14 ์ด์ƒ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋Š” WidgetKit์˜ ์ผ๋ถ€ ๊ธฐ๋Šฅ์ด๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์œ„์ ฏ์— ์‚ฌ์šฉ์ž์˜ ์ฃผ์‹ ์ˆ˜์ต๋ฅ ์„ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.
ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์—ฌ๋ถ€์— ๋”ฐ๋ผ ์œ„์ ฏ์„ ์—…๋ฐ์ดํŠธ ํ•ด์ฃผ์–ด์•ผ ํ•˜๋Š”๋ฐ, ์‚ฌ์šฉ์ž์˜ ๊ณ„์ •์ด ๋กœ๊ทธ์•„์›ƒ ๋˜๋”๋ผ๋„ ํƒ€์ž„๋ผ์ธ์ด ์ฆ‰๊ฐ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค๋Š” ๋ณด์žฅ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„๋กœ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ์‹œ WidgetCenter๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋‚˜ ์ƒํƒœ๋ฅผ ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ ์‹œ์ผœ์ค„ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

getCurrentConfigurations

ํ˜„์žฌ ์œ„์ ฏ์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

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)")
    }
}

reloadTimelines

WidgetKit์—์„œ ์‚ฌ์šฉ ์ค‘์ธ ํŠน์ • ์œ„์ ฏ์˜ ํƒ€์ž„๋ผ์ธ์„ ๋‹ค์‹œ ์ƒ์„ฑํ•  ๋•Œ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")

WidgetKit์—์„œ ์‚ฌ์šฉ ์ค‘์ธ kind์™€ ์ผ์น˜ํ•˜์—ฌ์•ผ ํ•˜๋Š”๋ฐ, kind๋Š” WidgetKit์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

reloadAllTimelines

ํŠน์ • ์œ„์ ฏ์ด ์•„๋‹Œ ์ „์ฒด WidgetKit์˜ ํƒ€์ž„๋ผ์ธ์„ ๋‹ค์‹œ ์ƒ์„ฑํ•ด ์ค„ ๋•Œ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

WidgetCenter.shared.reloadAllTimelines()

AppGroup

์ด๋ฒˆ์—๋Š” ์•ฑ์— ์ˆซ์ž๋ฅผ ์ฆ๊ฐ€์‹œ์ผœ 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์˜ ์œ„์ ฏ์„ ์—ฐ๊ฒฐํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€์— ๋Œ€ํ•ด์„œ๋„ ์ž‘์„ฑํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

๊ธด ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค !

profile
Flutter Developer

0๊ฐœ์˜ ๋Œ“๊ธ€

๊ด€๋ จ ์ฑ„์šฉ ์ •๋ณด