天天看點

iOS14 新特性 “Meet Widget”

Meet WidgetKit

Widgets 可以顯示你 App 相關的内容,使使用者可以快速通路您的應用以擷取更多詳細的資訊;一個 iOS App 可以提供多種樣式的 Widget ,使使用者可以專注于那些對自己最有價值的資訊;我們可以添加同一 Widget 的多個副本,進而根據其獨特的需求和布局定制每個 Widget;如果 Widget 中有自定義的功能,則使用者可以分别個性化 Widget;Widget 支援多種尺寸,你可以根據實際情況選擇适合自己的尺寸,在螢幕可用空間有限的情況下,Widget 呈現的資訊将是使用者最關心的。

在你的應用中添加 Widget

将 Widget 添加到 App 中需要進行少量的設定,并且将使用 SwiftUI 來展示他的内容。

  • 打開你的 Xcode 工程, 并且選擇 File > New > Target.
  • 在 Application Extension group 中選擇 Widget Extension
  • 輸入 Widget 的名字
  • 如果 Widget 提供了使用者可配置的屬性,請選中“ Include Configuration Intent ”複選框。
  • 點選完成
iOS14 新特性 “Meet Widget”

添加詳細配置資訊

Widget extension 模闆提供了符合 Widget 協定的初始化實作。Widget 體裡面的屬性确定 了 Widget 是否具有使用者可配置的屬性。

有兩種配置:

  • StaticConfiguration:對于沒有使用者可配置屬性的 Widget。例如,顯示一般市場資訊的股市 Widget,或顯示趨勢頭條的新聞 Widget。
  • IntentConfiguration:用于具有使用者可配置屬性的 Widget。例如,需要一個城市的郵政編碼的天氣 Widget,或者需要一個跟蹤号的包裹跟蹤 Widget。

Include Configuration Intent 複選框決定了 Xcode 使用哪種配置。當您選中此複選框時,Xcode 将使用 intent configuration ;否則,它使用靜态配置。要初始化配置,請提供以下資訊:

  • Kind:辨別 Widget 的字元串。這是您選擇的辨別符,并且應描述 Widget 所代表的内容。
  • Provider:符合 TimelineProvider 的對象,該對象生成一個時間線,告訴 WidgetKit 何時渲染。時間線包含自定義的 TimelineEntry 類型。TimelineEntry 辨別您希望 WidgetKit 更新 Widget 内容的日期,包括 Widget 視圖需要渲染自定義類型的屬性。
  • Placeholder View:WidgetKit 使用一個 SwiftUI 視圖來首次渲染。占位符是 Widget 的通用表示形式,沒有特定的配置或資料。
  • Content Closure:包含 SwiftUI 視圖的關閉。 WidgetKit 調用此方法來渲染 Widget 内容,并從 provider 傳遞 TimelineEntry 參數。
  • Custom Intent:定義使用者可配置屬性。

以下代碼顯示了一個 Widget,它為遊戲提供了正常的,不可配置的狀态:

@main
struct GameStatusWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.mygame.game-status",
            provider: GameStatusProvider(),
            placeholder: GameStatusPlaceholderView()
        ) { entry in
            GameStatusView(entry.gameStatus)
        }
        .configurationDisplayName("Game Status")
        .description("Shows an overview of your game status")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}      

在此示例中,Widget 将 GameStatusPlaceholder 用于placeholder view (這裡簡稱占位符視圖),并将 GameStatusView 用于 content closure。占位符視圖顯示您 Widget 的一般表示形式,使使用者可以大緻了解 Widget 的顯示内容。不要在占位符視圖中包含實際資料。例如,使用灰色框表示文本行,或使用灰色圓圈表示圖像。

Provider 為 Widget 生成 timeline,并在每個條目中包含遊戲狀态詳細資訊, 每個 timeline 條目的日期到達時,WidgetKit 都會調用 content closure 以顯示 widget 的内容。最後,修飾符指定 Widget 庫中顯示的名稱和描述,并允許使用者選擇小,中或大版本的 Widget。

請注意此 Widget 上 @main 屬性的用法。此屬性訓示 GameStatusWidget 是視窗小部件擴充的入口點,這意味着該擴充包含單個 Widget, 要支援多個小部件,請參閱在App Extension中聲明多個小部件。

Provide Timeline Entries

Timeline provider 會生成一個由時間線條目組成的時間線,每個條目都指定更新 Widget 内容的日期和時間。遊戲狀态 Widget 可能會定義其時間軸條目,以包含代表遊戲狀态的字元串,如下所示:

struct GameStatusEntry: TimelineEntry {
    var date: Date
    var gameStatus: String      

為了在 Widget 庫中顯示,WidgetKit 要求提供者提供預覽快照。

您可以通過檢查傳遞給 snapshot(for:with:completion :) 方法的 context 的 isPreview 屬性來辨別此預覽請求。當 isPreview 為 true 時,Widget 将在 WidgetKit 庫中顯示。作為響應,您需要快速建立預覽快照。如果您的 Widget 需要花費時間才能從伺服器生成或從伺服器擷取的資源或資訊,可以使用如下示例代碼:

struct GameStatusProvider: TimelineProvider {
    var hasFetchedGameStatus: Bool
    var gameStatusFromServer: String

    func snapshot(with context: Context, completion: @escaping (Entry) -> ()) {
        let date = Date()
        let entry: GameStatusEntry

        if context.isPreview && !hasFetchedGameStatus {
            entry = GameStatusEntry(date: date, gameStatus: "—")
        } else {
            entry = GameStatusEntry(date: date, gameStatus: gameStatusFromServer)
        }
        completion(entry)
    }      

請求初始 snapshot 後,WidgetKit調用時間軸(for:with:completion :) 來向 provider 請求正常時間軸。時間軸由一個或多個時間軸條目以及一個重載政策組成,該重載政策通知 WidgetKit 何時請求後續時間軸。

以下示例顯示了遊戲狀态 widget 的 provider 如何生成時間線,該時間線由伺服器上具有目前遊戲狀态的單個條目以及重載政策組成,以在15分鐘内請求新的時間線:

struct GameStatusProvider: TimelineProvider {
    func timeline(with context: Context, completion: @escaping (Timeline<GameStatusEntry>) -> ()) {
        // Create a timeline entry for "now."
        let date = Date()
        let entry = GameStatusEntry(
            date: date,
            gameStatus: gameStatusFromServer
        )

        // Create a date that's 15 minutes in the future.
        let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 15, to: date)!

        // Create the timeline with the entry and a reload policy with the date
        // for the next update.
        let timeline = Timeline(
            entries:[entry],
            policy: .after(nextUpdateDate)
        )

        // Call the completion to pass the timeline to WidgetKit.
        completion(timeline)
    }
}      

在此示例中,如果 Widget 不具有伺服器的目前狀态,則它可以存儲完成的引用,向伺服器執行異步請求以擷取遊戲狀态,并在該請求完成時調用完成。

在 Widget 中顯示内容

Widget 通常通過組合使用 SwiftUI 視圖定義内容。

當使用者從 Widget 庫中添加 Widget 時,他們從 Widget 支援的類型中選擇特定的系列(小,中或大),Widget 的 content closure 必須能夠渲染其支援的每個類型, WidgetKit 在 SwiftUI environment 中設定相應的系列和其他屬性,例如配色方案(淺色或深色)。

在上面顯示的遊戲狀态 Widget 的配置中,content closure 使用 GameStatusView 來顯示狀态。因為 Widget 支援所有三個小部件系列,是以它使用 widgetFamily 決定顯示哪個特定的 SwiftUI 視圖,如下所示:

struct GameStatusView : View {
    @Environment(\.widgetFamily) var family: WidgetFamily
    var gameStatus: GameStatus

    @ViewBuilder
    var body: some View {
        switch family {
        case .systemSmall: GameTurnSummary(gameStatus)
        case .systemMedium: GameStatusWithLastTurnResult(gameStatus)
        case .systemLarge: GameStatusWithStatistics(gameStatus)
        default: GameDetailsNotAvailable()
        }
    }
}      

Widget 僅顯示隻讀資訊,不支援互動元素,例如滾動元素或開關。在呈現 Widget 的内容時,WidgetKit 會忽略互動式元素。

當使用者與您的 Widget 互動時,WidgetKit 會激活您的應用程式,并傳遞您指定的URL, 當您的應用激活時,通過将使用者帶到相關位置來處理 URL。

在應用中申明多個 Widgets

例如,如果遊戲應用程式具有第二個用于顯示角色健康狀況的小部件,而第三個用于顯示排行榜,則将它們分組在一起,如下所示:

@main
struct GameWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        GameStatusWidget()
        CharacterDetailWidget()
        LeaderboardWidget()
    }
}      

結尾