以前的文章介紹了iOS14的Widget小組件的一些特性,以及如何建立靜態的Widget(StaticConfiguration)。咱們知道蘋果對於Widget表現的足夠剋制,致使iOS的Widget的交互能力很弱,只能展現靜態的東西,甚至連滾動列表、輸入文字都不能夠。雖然能夠經過時間線來作部分更新邏輯,可是對於用戶來講仍是靜態的,並且一種Widget能夠重複添加,若是不能用戶不能自定義配置,這種能夠重複添加的功能也就失去了意義。根據服務端狀態來提供可配置Widget,能夠帶給用戶更好的服務和更多元的交互體驗。html
爲了節省開發時間,網路部分橋接了主工程網絡組件,UI上簡單實現了一下主工程裏九宮格的效果,美觀上還有待調整。這次主要實現動態可配置。swift
先看下目前實現的動態配置小組件的Demo效果api
編輯狀態下:xcode
左:Samll,右:Medium
複製代碼
如下是選擇配置項的頁面:markdown
左:靜態配置,數據是本地寫死的,更新的話,須要發版。
右:按照服務端接口動態配置的可選項
複製代碼
建立動態配置的小組件,分爲兩種情:網絡
新建Widget擴展的時候勾選了Include Configuration Intent
,系統會自動建立出與Widget擴展名稱相同的 xxx.intentdefinition
配置文件,而且建立了一個能夠供Widget使用的自定義意圖Configuration
,Widget文件中的Provider
、SimpleEntry
、WidgetConfiguration
等都自動作了相應的配置,能夠直接用。app
現有的靜態的Widget,改爲可配置的。異步
第一種自動化程度比較高,新手作也不容易出錯。下邊咱們着重講一下第二種狀況。 靜態Widget改爲動態配置的小組件三個關鍵步驟:編輯器
一、在工程目錄中, 新建文件,選擇SiriKit Intent Definition File
,記得勾選member of the Targetide
二、點擊新建的xxx.intentdefinition
文件,Xcode顯示了一個空的意圖定義編輯器,點擊+
添加一個自定義意圖(Custom Intent),選擇New Intent
,命名爲LJZDMultibleConfigurationIntent
,記住這個名稱,很重要!
三、將Category設置爲View,並選擇「Intent is eligible for widgets」複選框,以代表小部件可使用該Intent。
注意:
一、注意自定義意圖的名稱 LJZDMultibleConfigurationIntent,Xcode編譯的時候,會自動生成相關的自定義意圖類相關的代碼。
二、後邊引用的自定義意圖的類名、協議都是根據這個名稱生成的。不是根據xxx.intentdefinition文件名生成的。
複製代碼
四、在Parameters
下,點擊+
添加參數,這裏分爲三大類:System Types、Enums、Types
Enums:枚舉類型,提供靜態的可選項,可選項數據是提早寫好的,要修改須要發版。
Types:提供動態類型的可選項,能夠從接口獲取數據。
複製代碼
選擇Types
類型以後,意圖定義編輯器裏會在CUSTOM INTENTS
下邊新增一個模塊:TYPES
。Types裏默認有兩個參數Identifier
,displayString
,能夠按需增長新的屬性。
蘋果的Code alone裏沒有增長額外屬性,他是經過`Identifier`來找到具體的數據模型。
我這裏新增了Widget須要的icon地址、跳轉主App具體頁面的路由地址、是否可用等屬性,知足了UI和跳轉的邏輯。
接口請求回來,直接建立對應的可選項意圖模型就能夠了。
複製代碼
以上就是配置意圖文件的部分。
意圖文件建立成功後,編譯工程,會自動生成一些代碼,後邊使用意圖的過程當中,會用到這些代碼
建立動態配置的Widget第二步,新增一個IntentsExtension的target。
這裏有三個關鍵步驟:
一、在Intents擴展的target裏,找到Supported Intents配置,增長前面配置的自定義意圖名稱LJZDMultibleConfigurationIntent
。
二、工程目錄裏找到xxx.intentdefinition
文件,勾選文件的Target Membership
,把xxx.intentdefinition
文件添加到IntentExtension的target裏。
重要:
必定要在File inspector中,驗證intent定義文件,也就是 xxx.intentdefinition 文件包含在應用程序、小部件擴展和intent擴展裏
複製代碼
三、在Xcode中添加了名稱爲IntentHandler
的意圖擴展後,工程會建立一個IntentHandler.swift
的文件,生成一個IntentHandler
類,裏面提供了處理數據的handler,在這裏須要實現 LJZDMultibleConfigurationIntent
類的 LJZDMultibleConfigurationIntentHandling
協議方法,這個方法裏去作獲取配置數據的操做。
下邊是LJZDMultibleConfigurationIntentHandling
協議的部分代碼,這些代碼是添加自定義意圖後,編譯工程纔會生成。
也能夠找到自定義意圖文件LJZDMultibleConfiguration
, 直接去Xcode生成的
LJZDMultibleConfigurationIntent
文件中查完整代碼。
...
@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable)
@objc(LJZDMultibleConfigurationIntentHandling)
public protocol LJZDMultibleConfigurationIntentHandling: NSObjectProtocol {
/*!
@abstract Dynamic options methods - provide options for the parameter at runtime
@discussion Called to query dynamic options for the parameter and this intent in its current form.
@param intent The input intent
@param completion The response block contains options for the parameter
*/
@available(iOS 14.0, macOS 10.16, watchOS 7.0, *)
@objc(provideMultChannelsOptionsCollectionForLJZDMultibleConfiguration:withCompletion:)
func provideMultChannelsOptionsCollection(for intent: LJZDMultibleConfigurationIntent, with completion: @escaping (INObjectCollection<MultChannel>?, Error?) -> Swift.Void)
....
複製代碼
首先咱們對比實現Widget的一組結構體的關鍵協議
協議 | WidgetConfiguration | TimelineProvider | getSnapshot | getTimeline |
---|---|---|---|---|
靜態 Widget | StaticConfiguration | TimelineProvider | func getSnapshot(in context: Self.Context, completion: @escaping (Self.Entry) -> Void) | func getTimeline(in context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> Void) |
可配置 Widget | IntentConfiguration | IntentTimelineProvider | func getSnapshot(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Self.Entry) -> Void) | func getTimeline(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> Void) |
把靜態Widget修改爲可配置的Widget:
一、修改 Widget 入口函數的 Configuration ,StaticConfiguration
-> IntentConfiguration
二、修改 TimelineProvider
協議 TimelineProvider
-> IntentTimelineProvider
三、修改對應協議的必要方法
按照順序修改對應的協議及方法,就能夠正常跑起來了。
至此,靜態Widget修改爲可選配置Widget就算完成了!
蘋果要求,Widget的視圖只能經過SwiftUI編寫,可是也不是全部的SwiftUI都能用。更不能使用UIViewRepresentable
或者 NSViewRepresentable
包裝的 UIKit 和 AppKit 視圖。具體可使用的SwiftUI視圖,能夠參考蘋果的開發文檔
Present your app’s content in widgets with SwiftUI views
單獨說一下Widget里加載圖片的問題,咱們先看下SwiftUI文檔中Image加載圖片的方式:
這裏沒有直接從網絡獲取圖片的方式,只能用UIImage去加載網絡圖片,而後經過init(uiImage: UIImage)
的方式建立 Image
平時咱們開發中利用UIImage
加載網絡圖片都是異步操做,給imageView一個佔位圖,而後去異步請求圖片信息,接口請求成功,再把圖片數據渲染回去。可是到了Widget這裏是行不通的,只能同步加載圖片。
獲取配置數據的流程如上圖,同步處理圖片的操做須要在渲染視圖以前,因而就放到了getTimeline的方法中,icon圖片較小,實際體驗速度仍是挺快的。
當用戶點擊Widget的時候,能夠直接跳轉到主App的某個相關頁面。蘋果提供了兩種方式:
* For all widgets, add the widgetURL(_:) view modifier to a view in your widget’s view hierarchy.
If the widget’s view hierarchy includes more than one widgetURL modifier, the behavior is undefined.
* For widgets using systemMedium or systemLarge, add one or more Link controls to your widget’s view hierarchy.
You can use both widgetURL and Link controls. If the interaction targets a Link control, the system uses the URL in that control.
For interactions anywhere else in the widget, the system uses the URL specified in the widgetURL view modifier.
複製代碼
說明一下主要區別:
widgetURL
,可是能夠有多個Link
控件。widgetURL
,以最後一個設置的爲準。small
類型的Widget也是能夠添加Link
控件的,至於蘋果爲何沒有提到,個人理解是small
類型的Widget已經很小了,不建議作複雜的視圖展現,使用一個widgetURL
徹底能知足使用。Widget經過widgetURL
、Link
控件打開主App,系統都會把URL
傳遞到onOpenURL(perform:), application(_:open:options:)
方法或者application(_:open:)
方法。
若是二者沒有使用,系統會傳遞一個NSUserActivity
對象給onContinueUserActivity(_:perform:)
, application(_:continue:restorationHandler:)
, 或者application(_:continue:restorationHandler:)
來響應。
NSUserActivity
對象包含了與用戶交互的Widget的詳細信息,若是Widget是可配置的,也會包含自定義意圖的配置信息。若是主App是用Swift寫的,用 WidgetCenter.UserInfoKey
來獲取,若是是OC寫的,用WGWidgetUserInfoKeyKind
和WGWidgetUserInfoKeyFamily
來獲取。
Extension程序跟主App進行數據通訊,須要配置好App Groups
,通訊方式以下:
具體使用區別算是老生常談的問題了,這裏就再也不贅述了。具體用法能夠點擊跳轉到官方文檔查看。
選擇Widget-extension對應的target,Xcode運行,就能夠把小組件會直接添加到設備的桌面上。可是若是你的supportedFamilies
沒有指定哪種的話,你是沒有辦法debug的。或者當你的Widget-extension使用WidgetBundle
支持多種Widget的時候,運行不起來,由於系統並不知道你要調試哪種Widget。咱們能夠經過設置Widget-extension的環境變量來解決這個問題。
找到Widget-extension的Edit Scheme,添加環境變量。
_XCWidgetKind
對應的 WidgetBundle
裏的須要調試的Widget
_XCWidgetFamily
對應Widget的尺寸,能夠設置爲 samll
,medium
,large
_XCWidgetDefaultView
對應Widget顯示的默認視圖,能夠設置爲 timeline
,snapshot
,placeholder
運行主App的時候,也能夠選擇Attach To Process
的方式調試擴展程序。
Xcode菜單欄選擇Debug->Attach To Process by Pid or Name
,而後填上須要調試的擴展的 Targe
名稱
這個問題頗有迷惑性,在實踐的過程當中,偶爾發生,偶爾又沒有,新建工程,勾選Configuration Intent並無什麼問題,問題是現有工程的接入問題:
針對問題靜態Widget改可配置,後文教程提到。現有的OC工程接入可配置Widget,關鍵點仍是OC-Swift的混編,添加Bridging-Header文件後,就找不到Intent了。在Intent配置頁面,配置好Intent以後,編譯一下,系統會自動生成XXXIntent
類,繼承自INIntent
。這裏有兩個關鍵點:
Intent類是xcode自動生成的,自動生成代碼,須要找到生成規則,增長OC-Swift混編頭文件後,系統默認的生成代碼語言爲Automatic
,Widget只支持Swift方式,在WidgetExtension的Target下,BuildSettings
找到Intent Definition Compiler
,指定語言爲Swift。
好比名稱是LJHotkeyConfiguration
,自動生成的類名是LJHotkeyConfigurationIntent
若是使用過程當中,仍是沒法定位到自動生成的類文件,須要使用重啓大法
,關閉工程,從新打開就能夠了
工程從新打開後,顏色也正常了,也能夠定位到類文件
以上是實現一個Widget須要用到的結構體、協議以及他們之間的依賴關係。小組件功能簡單,也足夠「小」,可是涉及的知識點仍是挺多的。但願此文對你們有幫助。
Widgets Code-along, part 1: The adventure begins
Widgets Code-along, part 2: Alternate timelines