1.創(chuàng)建項(xiàng)目
正常的創(chuàng)建項(xiàng)目流程,我使用的是Object-C語(yǔ)言。
創(chuàng)建項(xiàng)目完成后引入Widget Extension。
File -> New -> target-> Widget Extension ->Next
由于是加入一個(gè)新的Target煮仇,所以Widget的名字不能與項(xiàng)目名相同,也不能起成“Widget”(因?yàn)閃idget是一個(gè)已有的類名)谎仲,刪除時(shí)不能只是刪除文件還要在項(xiàng)目的Targets中刪除浙垫,起已經(jīng)刪除過(guò)一次的名字會(huì)報(bào)找不到文件的錯(cuò)誤。如果 Widget 支持用戶配置屬性(例如天氣組件郑诺,用戶可以選擇城市)夹姥,就需要勾選Include Configuration Intent這個(gè)選項(xiàng)。
創(chuàng)建后辙诞,會(huì)自動(dòng)生成5個(gè)struct和自帶的方法辙售。
2.探究自帶的方法用法
預(yù)覽視圖-Previews
代碼運(yùn)行的預(yù)覽視圖是SwiftUI新特性,會(huì)將運(yùn)行成果顯示在右邊的視圖上且支持熱更新飞涂,但是會(huì)很卡旦部,它不是Widget的必須部分,可以直接將其刪除或注釋较店。
struct WidgetView_Previews: PreviewProvider {
static var previews: some View {
WidgetViewEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
.previewContext(WidgetPreviewContext(family: .systemMedium))
}
}
需要注意:Widget只支持3種尺寸systemSmall (2x2)志鹃、 systemMedium (4x2)、 systemLarge(4x4)
同時(shí)泽西,一個(gè)APP可以支持多個(gè)不同的Widget。
數(shù)據(jù)提供-Provider
Provider是Widget最重要的部分缰趋,它決定了小組件的placeholder/getSnapshot/getTimeline這三種數(shù)據(jù)的顯示捧杉。在項(xiàng)目創(chuàng)建時(shí)勾選了Include Configuration Intent后的話陕见,Provider繼承自IntentTimelineProvider支持用戶自主編輯,沒(méi)有勾選則繼承自TimelineProvider不支持用戶自主編輯味抖。
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
官方的示例代碼的意思是:顯示從現(xiàn)在開始的5個(gè)小時(shí)的每個(gè)小時(shí)的時(shí)間评甜,再顯示完之后又重新運(yùn)行一次getTimeline。所以仔涩,我們只需要控制刷新時(shí)間的Calendar.Component的Value與entries中元素的個(gè)數(shù)忍坷,并設(shè)置TimeLine的 policy 就可以控制Widget的刷新時(shí)間,次數(shù)和方法熔脂。但是佩研,這個(gè)刷新次數(shù)是有蘋果官方是有限制的,5分鐘刷新一次是極限霞揉,低于5分鐘刷新官方會(huì)覺(jué)得刷新次數(shù)太多旬薯,高頻率的刷新也會(huì)導(dǎo)致耗電量的增加。
Timeline里面有三種方式:atEnd适秩,after(date)绊序,never
atEnd: timeline 中最后一個(gè) entry 顯示后更新。timelines 方法會(huì)重新調(diào)用秽荞。
after(date): 指定日期骤公,重新更新timeline。
never:系統(tǒng)不會(huì)自動(dòng)更新扬跋,除非我們主動(dòng)通過(guò) Widget Center Api 來(lái)更新阶捆。
例子:實(shí)現(xiàn)一個(gè)按秒刷新的時(shí)鐘,為了每一秒盡可能的準(zhǔn)確刷新就應(yīng)該向entries提供0-299這300秒的300個(gè)時(shí)間數(shù)據(jù)胁住,View展示時(shí)轉(zhuǎn)換成具體到秒的字符串展示即可趁猴,運(yùn)行一個(gè)周期后再次獲取5分鐘的時(shí)間數(shù)據(jù)。但是我測(cè)試了之后彪见,發(fā)現(xiàn)在刷新了一段時(shí)間之后儡司,小組件就不在刷新了,所以余指,如果要做定時(shí)器還是有問(wèn)題捕犬。
var currentDate = Date()
// 每5分鐘刷新一次
let refreshDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate)!
var arr:[SimpleEntry] = []
var tempDate = Date()
for idx in 0...300 {
tempDate = Calendar.current.date(byAdding: .second, value: idx, to: currentDate)!
let tempEntry = SimpleEntry(date: tempDate, configuration: configuration)
arr.append(tempEntry)
}
let timeline = Timeline(entries: arr, policy: .after(refreshDate))
completion(timeline)
也可以主動(dòng)刷新
//刷新所有 widget
WidgetCenter.shared.reloadAllTimelines
//或者
//刷新某一個(gè)widget. xxxx 是該widget的 identifier
WidgetCenter.shared.reloadTimelines(ofKind: "xxxx")
宿主App的數(shù)據(jù)有更新時(shí),可以主動(dòng)刷新 widget UI酵镜。具體怎么做呢碉碉?對(duì)于宿主App是 OC 寫的項(xiàng)目,需要下面兩步:
建立橋接文件
一般在新建 swift 文件時(shí) Xcode 會(huì)自動(dòng)彈出是否建立 bridge 文件的彈框淮韭,選擇創(chuàng)建垢粮。
也可以自己新建一個(gè) Header file 文件,然后指定 Targets-> Swift Compiler -> Object-C Bridging Header -> Header文件
建立swift文件
WidgetCenter是 swift 的類靠粪,這里我們新建一個(gè) swift 文件作為我們的刷新工具類, 里面代碼如下:
// 導(dǎo)入 widget kit 庫(kù)
import WidgetKit
//聲明 14以上的系統(tǒng)才可用此 api
@available(iOS 14.0, *)
//定義 oc 方法
@objcMembers final class WidgetKitHelper : NSObject {
class func reloadAllWidgets() {
// arm64架構(gòu)真機(jī)以及模擬器可以使用
#if arch(arm64) || arch(i386) || arch(x86_64)
WidgetCenter.shared.reloadAllTimelines()
#endif
}
}
刷新的時(shí)候蜡吧,直接調(diào)用
[WidgetKitHelper reloadAllWidgets]
數(shù)據(jù)共享
我們刷新 widget
是因?yàn)橛袛?shù)據(jù)更新了所以要刷新 UI毫蚓。那 widget
如何取出宿主App 的數(shù)據(jù)進(jìn)行刷新呢?也需要兩步:
建立
App Group
給App Group
定義一個(gè)唯一標(biāo)識(shí)比如:group.com.yourcompany.xxx
元潘,同時(shí),開發(fā)者證書的Capabilities
這一項(xiàng)也要把App Group
勾選上在
widget
中取出數(shù)據(jù)
UserDefaults(suiteName: "group.com.yourcompany.xxx").object(forKey: "xxxxxx")
下面是寫組件UI的地方
UI用的是SwiftUI君仆,具體語(yǔ)法可以參考這里https://gitee.com/TheAlgorithms/SwiftUI#HStack
struct WidgetViewEntryView : View {
var entry: Provider.Entry
static let taskDateFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
return formatter
}()
var body: some View {
Link(destination: URL(string: "widgetDemo://799")!){
VStack {
Text("\(entry.date, formatter: Self.taskDateFormat)")
}
.frame(height: 20)
}
Link(destination: URL(string: "widgetDemo://798")!){
VStack {
Text("I'm a Button")
}
.frame(height: 20)
}
}
}
通過(guò)Link標(biāo)記控件 點(diǎn)擊對(duì)應(yīng)控件,跳轉(zhuǎn)到appdeleget里,根據(jù)傳過(guò)來(lái)的url參數(shù),執(zhí)行對(duì)應(yīng)的操作导俘。需要注意呀伙,systemSmall,小的Widget不支持這種link點(diǎn)擊,小的Widget點(diǎn)擊之后 直接跳轉(zhuǎn)進(jìn)去app。
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options{
NSLog(@"%@",url.absoluteString);
if ([url.scheme isEqualToString:@"widgetDemo://799"]){
//執(zhí)行跳轉(zhuǎn)后的操作
}
return YES;
}
最后是小組件用戶手動(dòng)配置數(shù)據(jù)
創(chuàng)建小組件的時(shí)候,要勾選這個(gè)配置項(xiàng)参咙,intentdefinition的配置頁(yè)面,在下面的地方增加一個(gè)title屬性硫眯,String類型蕴侧,注意右邊的四個(gè)選項(xiàng)只需要勾選第2個(gè)。
回到代碼實(shí)現(xiàn)頁(yè)面两入,我們只要修改下WidgetEntryView的body里面的Text內(nèi)容净宵,從entry里面獲取到configuration配置的title屬性:
var body: some View {
// Text(entry.date, style: .time)
Text(entry.configuration.title == nil ? "沒(méi)有值" : entry.configuration.title!)
}
然后 就可以實(shí)現(xiàn)小組件顯示用戶輸入文字。