首先了解下如何創(chuàng)建
Xcode -> File -> New -> Target
找到 Widget Extension
如果你的 Widget
支持用戶配置屬性,則需要勾選這個(gè)(例如天氣組件,用戶可以選擇城市)鲁森,不支持的話則不用勾選
了解下創(chuàng)建Widget后砾嫉,系統(tǒng)給我們生成的文件內(nèi)容
下面這個(gè)代碼是沒(méi)有勾選 Include Configuration Intent
的地方
Provider
// Provider浅乔,顧名思義為小組件提供信息得一個(gè)struct
struct Provider: TimelineProvider {
public typealias Entry = SimpleEntry
// 編輯屏幕時(shí)秦效,左上角選擇添加小組件時(shí)候抱究,第一次展示小組件會(huì)走這個(gè)方法
public func snapshot(with context: Context, completion: @escaping (SimpleEntry) -> ()) {
}
// 這個(gè)方法內(nèi)可以進(jìn)行網(wǎng)絡(luò)請(qǐng)求恢氯,拿到的數(shù)據(jù)保存在對(duì)應(yīng)的 entry 中,調(diào)用 completion 之后會(huì)到刷新小組件
public func timeline(with context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
// 例如這是一個(gè)網(wǎng)絡(luò)請(qǐng)求
Network.request { data in
let entry = SimpleEntry(date: renderDate, data: data)
let timeline = Timeline(entries: [entry], policy: .after(nextRequestDate))
completion(timeline)
}
}
}
Entry
官方解釋: A type that specifies the date to display a widget, and, optionally, indicates the current relevance of the widget’s content.
// 我的理解是就是存儲(chǔ)小組件的數(shù)據(jù)的一個(gè)東西
struct SimpleEntry: TimelineEntry {
let date: Date
let data: Data
}
PlacehodlerView
// 這個(gè)是一個(gè)默認(rèn)視圖鼓寺,例如網(wǎng)絡(luò)請(qǐng)求失敗勋拟、發(fā)生未知錯(cuò)誤、第一次展示小組件都會(huì)展示這個(gè)view
struct PlaceholderView : View {
}
WidgetEntryView
// 這個(gè)是我們需要布局小組件長(zhǎng)什么樣子的view
struct StaticWidgetEntryView : View {
}
主入口
@main
struct StaticWidget: Widget {
private let kind: String = "StaticWidget"
public var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(), placeholder: PlaceholderView()) { entry in
StaticWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
支持多Widget樣式
@main
struct MainWidgets: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
Widget1()
Widget2()
}
}
勾選 Include Configuration Intent
之后可能出錯(cuò)的地方
如果你的app中設(shè)置了 Class Prefix
這下面這個(gè) ConfigurationIntent.self
則需要加上對(duì)應(yīng)的前綴
例如前綴是 XY
則需要修改為 XYConfigurationIntent.self
@main
struct MainWidget: Widget {
private let kind: String = "MainWidget"
public var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: XYConfigurationIntent.self, provider: Provider(), placeholder: PlaceholderView()) { entry in
IntentWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
處理Widget點(diǎn)擊事件
Widget
支持三種顯示方式妈候,分別是 systemSmall
指黎、 systemMedium
、 systemLarge
small
樣式只能用 widgetUrl
處理
@ViewBuilder
var body: some View {
ZStack {
AvatarView(entry.character)
.widgetURL(url)
.foregroundColor(.white)
}
.background(Color.gameBackground)
}
medium
和 large
可以用 Link
或者 widgetUrl
處理州丹,我們看到里面有四個(gè)相同的view,即左邊圖片杂彭,右邊文字的墓毒,這個(gè)view代碼如下(Link
方式)
struct RecipeView: View {
let recipe: RecipeModel
var body: some View {
Link(destination: URL(string: "你的網(wǎng)址")!) {
HStack {
WebImageView(imageUrl: recipe.squareImageUrl)
.frame(width: 65, height: 65)
Text(recipe.adjName + recipe.name)
.font(.footnote)
.bold()
.foregroundColor(.black)
.lineLimit(3)
}
}
}
}
添加 Link
或 widgetUrl
后,點(diǎn)擊每個(gè) RecipeView
都會(huì)觸發(fā)事件亲怠,這時(shí)候你需要在主項(xiàng)目中的 AppDelegate
中的如下方法進(jìn)行處理
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
}
關(guān)于Widget中加載網(wǎng)絡(luò)圖片的時(shí)機(jī)
當(dāng)我們?cè)?code>func timeline(withcompletion)這個(gè)方法中請(qǐng)求到數(shù)據(jù)拿到圖片鏈接后所计,必須同步把圖片解析出來(lái),否則直接讓對(duì)應(yīng)的WidgetView去load url 是加載不出來(lái)的
正確的寫(xiě)法
Struct Model {
...
let image: UIImage
}
func timeline(with context: Context, completion: @escaping (Timeline<LFPlanEntry>) -> ()) {
Network.request { data in
// 解析圖片
var image: UIImage? = nil
if let imageData = try? Data(contentsOf: url) {
image = UIImage(data: imageData)
}
let model = Model(image: image ?? defalutImage) // 這里給個(gè)默認(rèn)圖片
let entry = SimpleEntry(date: entryDate, data: model)
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
Struct WidgetView: View {
let model: Model
@ViewBuilder
var body: some View {
Image(uiImage: model.image)
.resizable()
}
}
錯(cuò)誤的寫(xiě)法(直接丟url給view去加載)
struct WidgetView : View {
let model: Model
@State private var remoteImage : UIImage? = nil
let defaultImage = UIImage(named: "default")!
var body: some View {
Image(uiImage: self.remoteImage ?? defaultImage)
.onAppear(perform: fetchRemoteImage)
}
func fetchRemoteImage() {
guard let url = URL(string: model.url) else { return }
URLSession.shared.dataTask(with: url){ (data, response, error) in
if let image = UIImage(data: data!){
self.remoteImage = image
} else {
print(error ?? "")
}
}.resume()
}
}