3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SwiftAdvent Calendar 2021

Day 9

プロジェクトにWidgetExtensionを追加して'WidgetFamily'ごとに表示してみる

Last updated at Posted at 2021-12-08

はじめに

ウィジェットの技術検証とキャッチアップを兼ねて社内で勉強会を開催しました。
今回は、WidgetExtensionを追加してWidgetFamilyごとに表示するところまでを記事にします。

環境

Swift 5.5
Xcode 13.1

サンプルプロジェクト

GitHubにPushしました。気になる方はご覧ください。
https://github.com/ken-sasaki-222/WidgetTraining
QR_961482.png

ウィジェットをローカルでビルドする

ウィジェット機能を利用したいアプリの作成

Xcode起動 → Create a new Xcode project → iOS → Appを選択して諸々設定
今回アプリ名はWidgetTrainingとします。

Widget Extensionの追加

File → New → Target → Widget Extensionを選択
今回はWidgetという名前で作成します。

Include Configuration Intentとは

Widget Extensionを追加するタイミングでInclude Configuration Intentという項目が表示されます。Configuration Intenを有効にすることで、ウィジェットで表示する内容をユーザーが選択できるようになります。
有効にしていない場合は、ユーザーが表示内容を選択できないタイプのウィジェットになります。

選択できる状態をIntentConfiguration、選択できない状態をStaticConfigurationと言います。

この内容は後ほどコードでも設定できるのでここでは概要を掴む程度で問題ありません。Finishを押してファイルを作成しましょう。

ウィジェットをビルドする

ファイルを作成したらWidget.swiftを確認しましょう。

Widget.swift
import WidgetKit
import SwiftUI
import Intents

struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent())
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration)
        completion(entry)
    }

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
}

struct WidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Text(entry.date, style: .time)
    }
}

@main
struct Widget: Widget {
    let kind: String = "Widget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            WidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

struct Widget_Previews: PreviewProvider {
    static var previews: some View {
        WidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

このタイミングで'Widget' is annotated with @main and must provide a main static function of type () -> Void or () throws -> Void.とエラーが発生した場合は、構造体Widgetを以下のように編集します。

Widget.swift
/// 省略///

@main
struct Widget: SwiftUI.Widget { // ここを編集する
    let kind: String = "Widget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            WidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

///省略///

これでウィジェットをローカルでビルドすることができます。
続いてWidgetFamilyを追加します。

Widget Familyの追加

systemSmallsystemMediumsystemLarge、3種類のWidgetFamilyを追加します。

Widget.swift
///省略///

@main
struct Widget: SwiftUI.Widget {
    let kind: String = "Widget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            WidgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) //ここを追加
    }
}

///省略///

このタイミングでウィジェットに表示されている時間(Date())をテキストに変更してみようと思います。
WidgetEntryViewEnvironmentObjectを追加して、WidgetFamilyごとにテキストを返します。

Widget.swift
///省略///

struct WidgetEntryView : View {
    @Environment(\.widgetFamily) var family: WidgetFamily

    var body: some View {
        switch family {
        case .systemSmall:
            return Text("small widget.")
        case .systemMedium:
            return Text("medium widget.")
        case .systemLarge:
            return Text("large widget.")
        @unknown default:
            return Text("default.")
        }
    }
}

///省略///

構造体Widgetも以下のように書き換えます。

Widget.swift
///省略///

@main
struct Widget: SwiftUI.Widget {
    let kind: String = "Widget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { _ in
            WidgetEntryView()
        }
        .configurationDisplayName("widget training.")
        .description("select widget")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

///省略///

続いてプレビューでも各WidgetFamilyが表示されるようにPreviewProviderを編集します。

Widget.swift
///省略///

struct Widget_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            WidgetEntryView()
                .previewContext(WidgetPreviewContext(family: .systemSmall))
            WidgetEntryView()
                .previewContext(WidgetPreviewContext(family: .systemMedium))
            WidgetEntryView()
                .previewContext(WidgetPreviewContext(family: .systemLarge))
            WidgetEntryView()
                .previewContext(WidgetPreviewContext(family: .systemExtraLarge))
        }
    }
}

動作確認

iPhoneホーム画面で
1種.002.jpeg
アプリ名を入力してウィジェットを検索
3種.001.jpeg
このように、systemSmall,systemMedium,systemLargeが表示されれば成功です。

おわりに

今回はWidgetExtensionを追加してWidgetFamilyごとに表示する方法を書きました。

ウィジェットはあまりキャッチアップできてなかったので良い機会になりました。
引き続きサンプルプロジェクトを肥やしていこうと思います。

ご覧いただきありがとうございました。
ここはこうしたほうがいい、ここはちょっと違うなどありましたらご教示いただければ幸いです

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?