iOS14 Widget開發與實踐

前言

以前的文章介紹了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文件中的ProviderSimpleEntryWidgetConfiguration等都自動作了相應的配置,能夠直接用。app

  • 現有的靜態的Widget,改爲可配置的。異步

第一種自動化程度比較高,新手作也不容易出錯。下邊咱們着重講一下第二種狀況。 靜態Widget改爲動態配置的小組件三個關鍵步驟:編輯器

  • 建立SiriKit Intent文件
  • 建立 Intents Extension 意圖擴展
  • 修改Widget文件

1、建立SiriKit Intent文件

一、在工程目錄中, 新建文件,選擇SiriKit Intent Definition File ,記得勾選member of the Targetide

建立配置文件

二、點擊新建的xxx.intentdefinition文件,Xcode顯示了一個空的意圖定義編輯器,點擊+添加一個自定義意圖(Custom Intent),選擇New Intent,命名爲LJZDMultibleConfigurationIntent,記住這個名稱,很重要!

新建Intent

三、將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和跳轉的邏輯。
接口請求回來,直接建立對應的可選項意圖模型就能夠了。
複製代碼

以上就是配置意圖文件的部分。

注意 \color{#DC143C}{注意}

意圖文件建立成功後,編譯工程,會自動生成一些代碼,後邊使用意圖的過程當中,會用到這些代碼

2、建立Intents Extension

建立動態配置的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)
....
複製代碼

3、修改Widget文件

首先咱們對比實現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 入口函數的 ConfigurationStaticConfiguration-> IntentConfiguration

二、修改 TimelineProvider協議 TimelineProvider-> IntentTimelineProvider

三、修改對應協議的必要方法

按照順序修改對應的協議及方法,就能夠正常跑起來了。

至此,靜態Widget修改爲可選配置Widget就算完成了!

SwiftUI 提供視圖

蘋果要求,Widget的視圖只能經過SwiftUI編寫,可是也不是全部的SwiftUI都能用。更不能使用UIViewRepresentable 或者 NSViewRepresentable包裝的 UIKitAppKit 視圖。具體可使用的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.
複製代碼

說明一下主要區別:

  • 一個widget視圖,只能有一個widgetURL,可是能夠有多個Link控件。
  • 若是一個Widget視圖內,多個控件都設置了widgetURL,以最後一個設置的爲準。
  • small類型的Widget也是能夠添加Link控件的,至於蘋果爲何沒有提到,個人理解是small類型的Widget已經很小了,不建議作複雜的視圖展現,使用一個widgetURL徹底能知足使用。

主App處理跳轉

Widget經過widgetURLLink控件打開主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寫的,用WGWidgetUserInfoKeyKindWGWidgetUserInfoKeyFamily來獲取。

共享數據

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的尺寸,能夠設置爲 samllmediumlarge

_XCWidgetDefaultView 對應Widget顯示的默認視圖,能夠設置爲 timeline ,snapshotplaceholder

Attach To Process

運行主App的時候,也能夠選擇Attach To Process的方式調試擴展程序。

Xcode菜單欄選擇Debug->Attach To Process by Pid or Name,而後填上須要調試的擴展的 Targe 名稱

可能遇到的問題

Configuration Intent找不到的問題

這個問題頗有迷惑性,在實踐的過程當中,偶爾發生,偶爾又沒有,新建工程,勾選Configuration Intent並無什麼問題,問題是現有工程的接入問題:

  • 不可配置的Widget修改爲可配置
  • 現有工程新建可配置Widget,新增、修改Intent

針對問題靜態Widget改可配置,後文教程提到。現有的OC工程接入可配置Widget,關鍵點仍是OC-Swift的混編,添加Bridging-Header文件後,就找不到Intent了。在Intent配置頁面,配置好Intent以後,編譯一下,系統會自動生成XXXIntent類,繼承自INIntent。這裏有兩個關鍵點:

1. Intent類自動生成器

Intent類是xcode自動生成的,自動生成代碼,須要找到生成規則,增長OC-Swift混編頭文件後,系統默認的生成代碼語言爲Automatic,Widget只支持Swift方式,在WidgetExtension的Target下,BuildSettings找到Intent Definition Compiler,指定語言爲Swift。

2. Intent命名規則

好比名稱是LJHotkeyConfiguration,自動生成的類名是LJHotkeyConfigurationIntent

若是使用過程當中,仍是沒法定位到自動生成的類文件,須要使用重啓大法,關閉工程,從新打開就能夠了

工程從新打開後,顏色也正常了,也能夠定位到類文件

總結

以上是實現一個Widget須要用到的結構體、協議以及他們之間的依賴關係。小組件功能簡單,也足夠「小」,可是涉及的知識點仍是挺多的。但願此文對你們有幫助。

參考資料

Building Widgets SampleCode

開發文檔

Widgets Code-along, part 1: The adventure begins

Widgets Code-along, part 2: Alternate timelines

Widgets Code-along, part 3: Advancing timelines

Add configuration and intelligence to your widgets

相關文章
相關標籤/搜索