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