



typealias를 통해 사용할 구조체를 정한다struct Provider: TimelineProvider {
typealias Entry = SimpleEntry

func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(
date: Date(),
emoji: "😀",
title: "플레이스 홀더 타이틀",
price: 1200000
)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(
date: Date(),
emoji: "😎",
title: "미리보기 타이틀",
price: 16000000
)
completion(entry)
}위젯 상태 변경 시점 (시간에 대한 핸들링)
뷰를 미리 렌더링하고 올린다. (위젯의 작동 방식 - Meet WidgetKit)
Widget 상태가 변경될 미래 시간이 포함된 timelineEntry 배열과 timeline 정책을 포함하고 있는 Timeline을 반환한다
.atEnd는 TimelineReloadPolicy 구조체에서 설정되어 있는 타입 프로퍼티로, 타임의 마지막 날짜가 지난 후, WidgetKit이 새로운 타임라인을 요청할 수 있도록 지정하는 정책 에 해당한다.
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
// 타임라인 배열
for hourOffset in 0 ..< 30 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(
date: entryDate,
emoji: "😇",
title: "타임라인 타이틀",
price: 2000000
)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
// .never : 요청 x
// .after : 특정 시간 이후
completion(timeline)
}
TimelineEntry 프로토콜을 채택한다date : 필수로 가져야 하며, 위젯이 다시 그려질 시간에 대한 정보를 갖는다relevance : 스마트 스택을 가진 위젯에서 위젯의 우선순위를 결정한다. (Score가 높은 위젯이 스택의 최상단으로 올라오도록 설정되어 있다)struct SimpleEntry: TimelineEntry {
let date: Date
let emoji: String
let title: String
let price: Int
}Provider를 통해 Entry를 제공받으면, Entry를 이용해서 위젯의 뷰를 그려준다
Entry를 매개변수로 가지는 SwiftUI View이기 때문에 원하는 UI를 자유롭게 구성할 수 있다
struct MyCoinOrderBookWidgetEntryView : View {
var entry: Provider.Entry // 프로퍼티로 Entry에 대한 정보를 넣어준다
var body: some View {
VStack {
Text(entry.date, style: .time)
Text(entry.emoji)
Text(entry.title)
Text(entry.price.formatted())
}
}
}
최종적으로 WidgetConfiguration을 구성한다
동일한 크기의 여러 위젯을 만들 수 있는데, kind는 위젯의 고유한 문자열이다.
.configurationDisplayName, description 을 통해 위젯 갤러리에서 보일 위젯의 이름과 설명을 설정할 수 있다
.supportedFamilies 를 통해 제공할 위젯의 크기를 설정할 수 있다
// 위젯의 정보
struct MyCoinOrderBookWidget: Widget {
let kind: String = "MyCoinOrderBookWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
MyCoinOrderBookWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
MyCoinOrderBookWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("보유 코인")
.description("실시간 시세를 확인하세요")
.supportedFamilies([.systemSmall, .systemLarge, .systemMedium])
}
}



extension UserDefaults {
static var groupShared: UserDefaults {
let appGroupID = "group.widgetTest.myCoinOrderBook"
return UserDefaults(suiteName: appGroupID)!
}
}
struct MyCoinOrderBookWidgetEntryView : View {
var entry: Provider.Entry // 프로퍼티로 Entry에 대한 정보를 넣어준다
var body: some View {
VStack {
Text(entry.date, style: .time)
Text(entry.emoji)
Text(UserDefaults.groupShared.string(forKey: "Market") ?? "기본값" )
Text(entry.title)
Text(entry.price.formatted())
}
}
}
.onAppear {
UserDefaults.groupShared.set(viewModel.market.koreanName, forKey: "Market")
}
WidgetCenter.shared.getCurrentConfigurations { response in
switch response {
case .success(let info):
print(info)
case .failure(let error):
print(error)
}
}
필요한 시점에 위젯을 업데이트할 수 있다
.onAppear {
viewModel.fetchOrderBook()
print("----- 현재 활성화 되어 있는 위젯 -----")
WidgetCenter.shared.getCurrentConfigurations { response in
switch response {
case .success(let info):
print(info)
case .failure(let error):
print(error)
}
}
print("이전 : ", UserDefaults.groupShared.string(forKey: "Market"))
UserDefaults.groupShared.set(viewModel.marketData.koreanName, forKey: "Market")
print("이후 : ", UserDefaults.groupShared.string(forKey: "Market"))
WidgetCenter.shared.reloadTimelines(ofKind: "MyCoinOrderBookWidget")
}
