📚

WidgetKitを学ぶ

2022/11/06に公開

これはなに

WidgetKit について勉強した内容をまとめたもの。
定期的に更新する予定。

WidgetKitの使い方を俯瞰する

Appleのドキュメント を読み進める。
Rootで WidgetConfiguration を生成して Widget を構築していく。
WidgetConfiguration には、

  • StaticConfiguration : ユーザーがカスタマイズ不可の Widget
  • IntentConfiguration : ユーザーがカスタマイズ可能な Widget

の2つがある。StaticConfiguration の場合を例に全体像を把握する。

サンプルコードと解釈

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

Widget にコンテンツが表示されるまでの流れは、こんな感じ。

  1. GameStatusProviderWidget に表示するコンテンツのリストを時系列順に生成する
  2. StaticConfiguration のinitializerのクロージャーが呼ばれて、コンテンツが順に渡される
  3. 上記のクロージャー内でViewが生成されて、ユーザーに表示される

上記の流れで出てきたコンテンツのリストを Timeline 、1つのコンテンツを TimelineEntity と呼ぶ。

最後にmodifierで Widget の設定をする。

  • configurationDisplayName: 表示名
  • description: Widget の説明
  • supportedFamilies: Widget のサイズ

ここまでできたら後は、 ProviderView を実装していく。

Providerの実装

Provider についてまとめると、

  • Widget で表示する Timeline を生成する。
    • Timeline の要素は、 TimelineEntity を継承する
  • ProviderTimelineEntity を継承する
  • Widgetの一覧に表示する際のpreview用にsnapshotを渡す必要があって、 getSnapshot(in:completion:) を使う
  • getSnapshot(in:completion:) が呼ばれてpreview用のsnapshotを渡した後に、 getTimeline(in:completion:) が呼ばれて実データを渡す

getSnapshot(in:completion:)のサンプルと注意点

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

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

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

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

context.isPreview = true の時に、Widget一覧に表示される。

getTimeline(in:completion:)のサンプルとメモ

struct GameStatusProvider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<GameStatusEntry>) -> Void) {
        // 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)
    }
}

getTimeline(in:completion:) で実装するのは下記の2つ。

  • Widget に表示するコンテンツデータを生成
    • 必要に応じてサーバーと通信したりする
  • 更新する時のポリシーを指定する

WidgetのContentに関する注意点

基本的は View に関してはSwiftUIで実装していくだけだが注意点がある。

最後に

Widget に関して公式のリファレンスを読んで理解した内容をまとめました。
解釈が間違ってる可能性は十分にあるので、間違ってる点はコメントいただけると助かります。

今後も勉強した内容を追記していきます!

Discussion