π μ κΈνλ©΄ μμ ― λ§λ€κΈ° - 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μ μμ ―μ μ°κ²°ν΄ μ¬μ©ν μ μλμ§μ λν΄μλ μμ±ν΄ 보λλ‘ νκ² λ€.
κΈ΄ κΈ μ½μ΄μ£Όμ μ κ°μ¬ν©λλ€ !