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…