什么是widget
iOS Widget是一種可以放置在 iOS 設(shè)備主屏幕上的小型應(yīng)用程序,提供了一個快速訪問和瀏覽信息的方式。 Widget可以顯示各種類型的內(nèi)容,例如天氣、日歷、媒體播放器、時鐘等等。
iOS Widget 的最大特點是可以在主屏幕上快速查看并與應(yīng)用程序進(jìn)行交互,而無需打開應(yīng)用程序。 Widget 需要使用 WidgetKit 框架來創(chuàng)建和管理,WidgetKit提供了各種各樣的組件和 API,使得開發(fā)者可以在 Widget中實現(xiàn)豐富的功能和交互體驗。
本文要實現(xiàn)的Demo效果如下圖所示,顯示的是當(dāng)前時間的Widget。

新建widget
首先直接新建一個target,因為widget實際上就是一個target,所顯示的數(shù)據(jù)是依賴宿主App的,創(chuàng)建完widget后xcode會幫我們新建好示例代碼。

示例代碼中有幾個類了解才能知道如何正確構(gòu)建widget。
TimelineEntry
這個類是用來給widget提供數(shù)據(jù)的,我們可以簡單理解為提供數(shù)據(jù)的demo,唯一的要求是必須要有一個表示時間的屬性var date: Date { get },這個屬性的作用我們稍后解釋,定義一個TimelineEntry結(jié)構(gòu)體如下:
struct SimpleEntry: TimelineEntry {
let date: Date
var hour : Int {
Calendar.current.component(.hour, from: self.date)
}
var minute: Int {
Calendar.current.component(.minute, from: self.date)
}
}
其中hour和minute變量用來顯示當(dāng)前時間的小時和分鐘。
TimelineProvider
這是一個協(xié)議,還是看一段官方的解釋:
A type that advises WidgetKit when to update a widget's display.At various times, WidgetKit requests a timeline from the provider. A timeline is an array of objects conforming to <doc:TimelineEntry>. Eachtimeline entry has a date, and you can specify additional propertiesfor displaying the widget.
這個協(xié)議告訴WidgetKit什么時候更新widget,就是給WidgetKit提供數(shù)據(jù)源,數(shù)據(jù)源就是上面的TimelineEntry實體,當(dāng)然還包括更新策略也就是在什么時間需要更新,協(xié)議里面有幾個方法需要實現(xiàn):
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
這個方法是提供一個站位視圖,因為我們?yōu)?code>WidgetKit提供的數(shù)據(jù)有可能是異步請求的,這時如果添加widget時,數(shù)據(jù)可能還沒準(zhǔn)備好,此時我們可以提供一個站位視圖,讓用戶知道大概得widget樣子,這里簡單的用當(dāng)前時間初始化了SimpleEntry。
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
這個方法是提供靜態(tài)快照,當(dāng)我們添加widget時我們需要在widget gallery中提供一個快照。
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let date = Date().zeroSeconds!
for hourOffset in 0 ..< 60 {
let entryDate = Calendar.current.date(byAdding: .minute , value: hourOffset, to: date)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
這個方法是提供一組數(shù)據(jù)用于顯示,這里我們提供了基于當(dāng)前時間的60組數(shù)據(jù),每個entry數(shù)據(jù)的date間隔一分鐘,并放入了一個entries數(shù)組里面,并告知了刷新策略是.atEnd,也就是在60組數(shù)據(jù)顯示完后再更新,此時又會基于當(dāng)前時間提供60組數(shù)據(jù),每個entry數(shù)據(jù)的date值間隔一分鐘,循環(huán)如此。
可以發(fā)現(xiàn)
getSnapshot和getTimeline都利用逃逸閉包來提供entry,這表明方法里面可以進(jìn)行異步的操作,例如可以進(jìn)行網(wǎng)絡(luò)請求,在網(wǎng)絡(luò)請求成功后構(gòu)建entry在利用閉包返回,而placeholder則沒有提供逃逸閉包,所以這里盡量提供簡單的數(shù)據(jù)。
構(gòu)建view
利用上面的幾個類組合起來構(gòu)建view:
struct widgetTimeEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
HStack {
Text("時: " + String(entry.hour))
.font(Font.system(size: 30))
.foregroundColor(Color.blue)
.background(Color.gray)
Spacer()
}
HStack {
Spacer()
Text("分:" + String(entry.minute))
.font(Font.system(size: 30))
.foregroundColor(Color.orange)
.background(Color.gray)
}
Text(entry.date, style: .timer)
}
.padding()
}
}
@main
struct widgetTime: Widget {
let kind: String = "widgetTime"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
widgetTimeEntryView(entry: entry)
}
.configurationDisplayName("WidgetDemo")
.description("This is an example widget.")
}
}
view利用provider提供的entry來顯示,下面的kind是唯一標(biāo)識符,WidgetKit用kind來管理widget,StaticConfiguration是一個配置類,這里需要傳入kind和provider,在閉包中需要返回entryView,configurationDisplayName和description是當(dāng)我們添加時widget顯示的名字和描述信息。
總結(jié)
本文簡單的介紹了iOS中widget的構(gòu)建,當(dāng)然widget和宿主之間是可以進(jìn)行通信的,比如共享數(shù)據(jù)同時widget也可以和用戶完成交互后,利用用戶的交互信息在更新狀態(tài),widget的使用主要需要明白entery的構(gòu)建和其中通過provider如何進(jìn)行流轉(zhuǎn)的。