2020年06月22日的WWDC上iOS14的新特性-小部件正式在iOS上線,同時WidgetKit也正式面向廣大開發者使用。git
也正是由於對Android的小部件有所瞭解,故想嘗試下iOS的小部件的開發,而且發現當前並無相關的文章,故記錄下我學習WigetKit的經歷,如下均爲本身學習路上的經歷,可能會有些問題,還望大佬指正。github
同時已把學習路上寫的代碼開源 - iWiget,看完這篇文章認爲有用就點個Star唄!json
項目地址: github.com/Littleor/iW…swift
沒有看過前一節的建議看看這個: (iOS14)WidgetKit開發實戰1-初識iOS小部件 api
使用WigetKit開發Widget的主要就是View、Provider、Data。簡單來講,獲取到Data以後使用Provider顯示在View上就是Widget了。bash
一言小部件的View多是這三個當中最簡單的部分了,這裏爲了方便理解直接使用一個Text來表示吧。markdown
struct OneWordView: View { var content:String = "每日一言" var body: some View { Text(content) } } 複製代碼
如上述代碼,content用來控制顯示內容,一個最簡單的Text顯示便可,還能靠Swift實現自動暗黑模式。 這裏對View就很少談了,這部分View比較容易。ide
一言部件的數據如何獲取?從哪來? 目前iWidget採起的方案是經過Hitokoto提供的接口來獲取對應的數據。oop
不得不說,Hitokoto的接口很方便有質量。post
import Foundation struct OneWord { let content: String let length: Int } struct OneWordLoader { static func fetch(completion: @escaping (Result<OneWord, Error>) -> Void) { let oneWordURL = URL(string: "https://v1.hitokoto.cn/")! let task = URLSession.shared.dataTask(with: oneWordURL) { (data, response, error) in guard error == nil else { completion(.failure(error!)) return } let oneWord = getOneWordInfo(fromData: data!) completion(.success(oneWord)) } task.resume() } static func getOneWordInfo(fromData data: Foundation.Data) -> OneWord { let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] let content = json["hitokoto"] as! String let length = json["length"] as! Int return OneWord(content: content, length: length) } } 複製代碼
其中OneWord
聲明瞭解析事後的數據類型,經過OneWordLoader
的fetch
方法請求API獲取JSON數據後經過內部的getOneWordInfo
方法解析JSON數據返回OneWord
數據。
這部分代碼我封裝到了iWidget/Data/OneWordData.swift中,具體可見GitHub。
Provider的做用主要用於控制Widget的刷新 一言的Provider中,咱們須要在獲取API數據後再給Widget刷新顯示,故大概流程爲: 獲取數據->獲取成功後刷新數據。
struct OneWordProvider: IntentTimelineProvider { public func snapshot(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (OneWordEntry) -> ()) { let entry = OneWordEntry(date: Date(),data: OneWord(content: "一言", length: 2)) completion(entry) } public func timeline(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (Timeline<Entry>) -> ()) { let currentDate = Date() let refreshDate = Calendar.current.date(byAdding: .minute, value: 60, to: currentDate)! OneWordLoader.fetch { result in let oneWord: OneWord if case .success(let fetchedData) = result { oneWord = fetchedData } else { oneWord = OneWord(content: "獲取失敗", length: 4) } let entry = OneWordEntry(date: currentDate,data: oneWord) let timeline = Timeline(entries: [entry], policy: .after(refreshDate)) completion(timeline) } } } 複製代碼
其中
let timeline = Timeline(entries: [entry], policy: .after(refreshDate))
entries提供了下次更新的數據,policy提供了下次更新的時間。
其中policy
可填.never
永不更新(可經過WidgetCenter
更新)、.after(Date)
指定多久以後更新、.atEnd
指定Widget經過你提供的entries的Date更新。
我的被這個坑卡了好久,後來看了WidgetKit的代碼才找到緣由,這個真心坑。 當使用預覽的時候編譯會報錯:
reference to invalid associated type 'Entry' of type 'Provider' 複製代碼
這是由於你啓用了預覽:
struct MainWidget_Previews: PreviewProvider {
static var previews: some View {
PayToolsEntryView(entry: SimpleEntry(date: Date()))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
複製代碼
對於這個問題我目前只想到了2個辦法解決:
在編譯前註釋Preview的代碼便可,須要預覽再解除
直接在Provider中添加:
typealias Entry = SimpleEntry
複製代碼
其中SimpleEntry須要使用你的Entry的變量名替換。
這一次大概整理了下本身開發一個Widget的大概過程,還有可配置小部件和小部件Link等操做下次再分享,敬請期待。
完整代碼見GitHub
後續還會慢慢完善WidgetKit開發的文章,同時iWiget也會不斷完善,這篇文章對你有用就點個Star吧!
項目地址: github.com/Littleor/iW…