WWDC Quick Look 💓 By SwiftGGTeam
Bring widgets to new places

Bring widgets to new places

元の動画を見る

ハイライト

iOS 17 と macOS Sonoma では StandBy、デスクトップ Widget、iPad のロック画面など新しい表示場所が追加されました。WidgetKit には containerBackgroundcontentMarginswidgetAccentablecontainerBackgroundRemovable などの API が加わり、Widget が各環境の背景・余白・描画スタイルに自動適応できるようになりました。


主要内容

課題: Widget はかつてホーム画面だけだった

iOS 14 で WidgetKit が登場したとき、Widget の表示場所はホーム画面だけでした。開発者は 1 種類の背景スタイル、1 セットの余白ルール、1 つの色の描画方式だけを考えればよかったのです。

状況は変わりました。iOS 17 では StandBy モードが追加され、iPhone を横向きに充電すると Widget がダークな全画面 UI に表示されます。macOS Sonoma では Widget をデスクトップに直接置けます。iPad のロック画面でも Widget が使えるようになりました。

新しい表示場所ごとに見た目の要求が異なります。StandBy ではシステムが Widget をインテリジェントに着色します。デスクトップ Widget では背景を外して壁紙に溶け込ませる必要があるかもしれません。ホーム画面 Widget は従来どおり鮮やかな背景を保つべきです。1 つのコードベースですべての環境に対応するのが、新たな課題になりました。

Apple のアプローチ: 環境を意識した Widget API

WidgetKit には 3 つの中核機能が追加されました。

  1. コンテナ背景containerBackground): Widget の背景がどうあるべきかを宣言し、表示場所に応じてシステムが調整します。
  2. コンテンツ余白の無効化contentMarginsDisabled): 端から端まで描画が必要な場合(StandBy など)、開発者が自分でパディングを制御します。
  3. アクセント色の描画widgetAccentable): StandBy などのダークなスマート着色モードで、システムが着色すべきテキストと要素を指定します。

さらに containerBackgroundRemovable があり、デスクトップ Widget の背景をシステムが除去できるため、壁紙に透明に溶け込む表示が可能になります。

共通の考え方は、開発者が意図を宣言し、システムが実行環境に応じて具体的な描画を決める、というものです。


詳細

デフォルト余白を無効化する(02:08

iOS 17 では、Widget にはデフォルトでシステムが追加するコンテンツ余白があります。レイアウトを完全に制御したい Widget — 単色やグラデーションで Widget 全体を埋めるようなもの — では、このデフォルト余白が不自然な空白を残します。

contentMarginsDisabled() でデフォルト余白をオフにし、widgetContentMargins 環境値でパディングを手動制御します。

struct SafeAreasWidgetView: View {
    @Environment(\.widgetContentMargins) var margins

    var body: some View {
        ZStack {
            Color.blue
            Group {
                Color.lightBlue
                Text("Hello, world!")
            }
                .padding(margins)
        }
    }
}

struct SafeAreasWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(...) {_ in
            SafeAreasWidgetView()
        }
        .contentMarginsDisabled()
    }
}

キーポイント:

  • WidgetConfigurationcontentMarginsDisabled() を呼び、システム余白を無効にします。
  • @Environment(\.widgetContentMargins) でシステムが適用するはずだった余白値を読み取り、必要な場所に padding を追加できます。
  • 全面背景の Widget に向いたパターンです。内側コンテンツに手動で余白を付け、外側の背景は Widget 全体を覆います。

コンテナ背景: 1 つのコードですべての場所に対応(03:19

containerBackground(for:alignment:content:) は iOS 17 / macOS Sonoma で最も重要な WidgetKit の新 API です。View に直接背景を設定する従来のやり方に代わります。

struct EmojiRangerWidgetEntryView: View {
    var entry: Provider.Entry

    @Environment(\.widgetFamily) var family

    var body: some View {
        switch family {
        case .systemSmall:
            ZStack {
                AvatarView(entry.hero)
                    .widgetURL(entry.hero.url)
                    .foregroundColor(.white)
            }
            .containerBackground(for: .widget) {
                Color.gameBackground
            }
        }
        // additional cases
    }
}

キーポイント:

  • containerBackground(for: .widget) は「この部分が Widget の背景である」とシステムに伝えます。表示環境に応じて描画が変わります。
  • StandBy では背景が除去または薄くされることがあります。デスクトップではユーザーが背景を外して壁紙に溶け込ませることもできます。
  • for: .widget を付けない通常の背景(ZStack 内の Color.blue など)は、システムのスマート調整の対象になりません。
  • @Environment(\.widgetFamily) で small / medium / large / accessory 系のサイズを区別し、1 つの Widget エントリでレイアウトを分岐できます。

アクセント色: StandBy での自動着色(03:48

StandBy モードでは、システムが Widget をスマートに着色し、カラフルなコンテンツをダーク環境に合ったモノクロやデュオトーンに変換します。すべての要素を着色すべきではありません。widgetAccentable() でシステムが着色すべきテキストを指定します。

var body: some View {
    switch family {
    case .accessoryRectangular:
        HStack(alignment: .center, spacing: 0) {
            VStack(alignment: .leading) {
                Text(entry.hero.name)
                    .font(.headline)
                    .widgetAccentable()
                Text("Level \(entry.hero.level)")
                Text(entry.hero.fullHealthDate, style: .timer)
            }.frame(maxWidth: .infinity, alignment: .leading)
            Avatar(hero: entry.hero, includeBackground: false)
        }
        .containerBackground(for: .widget) {
            Color.gameBackground
        }
    // additional cases
}

キーポイント:

  • widgetAccentable() でテキストをマークすると、StandBy の着色モードでシステムが優先的に着色します。
  • マークされていないテキストは着色時に控えめに表示され、視覚的な階層が生まれます。
  • accessoryRectangular はロック画面と StandBy で使われるコンパクトな矩形サイズです。
  • この modifier はシステムがスマート着色を行うときだけ効きます。通常のホーム画面 Widget には影響しません。

除去可能な背景: macOS デスクトップに溶け込む(04:22

macOS Sonoma では Widget をデスクトップに置けます。ユーザーは Widget の背景を除去して、デスクトップの一部のように見せることもできます。写真 Widget では背景を外すと、写真が壁紙の上に浮いているように見えます。

struct PhotoWidget: Widget {
    public var body: some WidgetConfiguration {
        StaticConfiguration(...) { entry in
            PhotoWidgetView(entry: entry)
        }
        .containerBackgroundRemovable(false)
    }
}

キーポイント:

  • containerBackgroundRemovable(false) は「この Widget の背景は除去できない」と宣言します。写真 Widget は通常 false にします。写真自体がコンテンツなので、背景を外すと Widget が消えたのと同じになります。
  • true(デフォルト)の場合、macOS デスクトップでユーザーが Widget 背景を除去できます。
  • この API が目に見える効果を持つのは macOS Sonoma だけですが、すべてのプラットフォームで同じコードを書けます。システムがプラットフォーム差を処理します。

重要ポイント

これらの新 API を使って取り組める方向の例です。

  • StandBy 専用時計 Widget を作る: widgetAccentable() で StandBy の着色モードでも時刻数字を目立たせ、装飾要素は薄くする。containerBackground(for: .widget) でダーク背景を用意し、StandBy の暗い環境でネイティブに見せる。入口は StaticConfiguration + accessoryRectangular レイアウト。

  • 既存 Widget のデスクトップ体験をアップグレードする: ホーム画面 Widget があるなら、2 行追加するだけで macOS Sonoma デスクトップでもよく見える。背景色をすべて .containerBackground(for: .widget) に移し、重要なテキストに .widgetAccentable() を付ける。プラットフォーム分岐は不要。

  • デスクトップ向け写真 Widget を作る: containerBackgroundRemovable(false) で写真 Widget の背景除去を防ぐ。ユーザーが Widget をデスクトップにドラッグしても、写真が壁紙にシームレスに溶け込む。PhotoPicker と組み合わせて表示アルバムを選ばせる。

  • 「余白なし」天気 Widget を作る: contentMarginsDisabled() + widgetContentMargins で余白を手動制御し、グラデーション背景が Widget 全体を覆う効果を実現する。没入感のある天気表示 — 空のグラデーションが端から端まで — に向いています。

  • 既存 Widget に accessory 系サイズを追加する: systemSmall/Medium/Large だけなら、accessoryRectangularaccessoryCircular の case を追加して iPhone のロック画面と StandBy に出せる。データモデルは再利用し、レイアウトだけ変える。


関連セッション

コメント

GitHub Issues · utterances