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 ”複選框。
  • 點擊完成

image

添加詳細配置信息

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()
    }
}

結尾

iOS 用戶終於不必再像過去那樣進入應用程序內獲取天氣、新聞資訊、日期等信息,可直接通過在主界面上添加不同應用、不同尺寸的組件,關鍵信息就可直接在主屏幕上一目瞭然,有點致敬安卓的影子。

好了,今天的講解就到這裏,感興趣的朋友可以關注我的技術公衆號,每週都有優質技術文章推送,微信掃一掃下方二維碼即可關注:

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章