1
1

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 1 year has passed since last update.

WidgetKitで天気予報アプリ作ってみた〜Widget Extension追加編〜

Last updated at Posted at 2022-02-03

投稿の経緯

前回投稿したWidgetKitで天気予報アプリ作ってみた~位置情報取得&保存編~の続編です。
今回は「Widget Extension」を追加して「WidgetBundle」を設定するところまでを書こうと思います。

前回の記事を見てない方は先に↓こちら↓を確認してください。

開発環境

Swift 5.5
Xcode 13.2.1

サンプルプロジェクト

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

Widget Extensionの追加

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

Include Configuration Intentとは

スクリーンショット 2022-02-02 22.49.58.png
Widget Extensionを追加するタイミングで「Include Configuration Intent」という項目が表示されます。こちらを有効にすることで、ウィジェットで表示する内容をユーザーが選択できるようになります。
有効にしていない場合は、ユーザーが表示内容を選択できないタイプのウィジェットになります。

選択できる状態を「IntentConfiguration」、選択できない状態を「StaticConfiguration」と言います。
今回は有効にしなくて大丈夫です。そのまま「Widget Extension」を追加しましょう。

Widgetファイルを分割する

Widget.swift
import WidgetKit
import SwiftUI

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date())
    }

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

    func getTimeline(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)
            entries.append(entry)
        }

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

struct SimpleEntry: TimelineEntry {
    let date: Date
}

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 {
        StaticConfiguration(kind: kind, 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()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

「Widget Extension」を追加するとこのようにWifget.swiftファイルが作成されます。

これは好みの問題ですが、このファイルにまとめてEntryやViewを書いてしまうと見通しが悪くなるのでいくつかのファイルに分けたいと思います。

それでは順番に対応しましょう。

TimelineProvider

MediumWidgetProvider.swift
import WidgetKit
import SwiftUI

struct MediumWidgetProvider: TimelineProvider {
    func placeholder(in context: Context) -> MediumWidgetEntryModel {
        MediumWidgetEntryModel(date: Date())
    }

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

    func getTimeline(in context: Context, completion: @escaping (Timeline<MediumWidgetEntryModel>) -> ()) {
        var entries: [MediumWidgetEntryModel] = []

        // 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 = MediumWidgetEntryModel(date: entryDate)
            entries.append(entry)
        }

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

MediumWidgetProviderファイルを作成してコードを移行します。
ミディアムサイズのウィジェット専用の「TimelineProvider」です。このstructはウィジェットで扱うデータを定義して、タイムラインにエントリーします。

TimelineEntry

MediumWidgetEntryModel.swift
import WidgetKit
import SwiftUI

struct MediumWidgetEntryModel: TimelineEntry {
    let date: Date
}

MediumWidgetEntryModelファイルを作成してコードを移行します。
ミディアムサイズのウィジェットのタイムラインに渡す「TimelineEntry型」のモデルとして扱います。

View

MediumWidgetView.swift
import WidgetKit
import SwiftUI

struct MediumWidgetView: View {
    @Environment(\.colorScheme) var colorScheme
    
    var body: some View {
        Text("Hello, World!")
    }
}

struct MediumWidgetView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            MediumWidgetView()
                .previewContext(WidgetPreviewContext(family: .systemMedium))
                .environment(\.colorScheme, .light)
            MediumWidgetView()
                .previewContext(WidgetPreviewContext(family: .systemMedium))
                .environment(\.colorScheme, .dark)
        }
    }
}

WidgetEntryViewを削除して新しくMediumWidgetViewを作成します。ウィジェットのViewを管理します。
このタイミングで@Environment(\.colorScheme) var colorSchemeを定義して、プレビュー画面でダークモードとライトモード両方のViewを確認できるようにしておきましょう。後々便利です。

Widget

MediumWidgetMaster.swift
import WidgetKit
import SwiftUI

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

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: MediumWidgetProvider()) { entry in
            MediumWidgetView()
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
        .supportedFamilies([.systemMedium])
    }
}

新しくMediumWidgetMasterファイルを作成してコードを移行します。このタイミングで.supportedFamiliesでミディアムサイズを指定しておきましょう。

WidgetBundleの設定

コチラの記事と

公式ドキュメントがわかりやすいです。

「WidgetBundle」の中に複数のウィジェットを配置することで、異なるレイアウトのウィジェットが作りやすくなります。
現状はミディアムサイズのウィジェットのみの開発ですが、いつでも別サイズを追加できるように「WidgetBundle」を設定しておきましょう。

WidgetBundle.swift
import WidgetKit
import SwiftUI

@main
struct WidgetBundle: SwiftUI.WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        MediumWidgetMaster()
    }
}

これで設定完了です。WidgetBundleに@mainを付与して、WidgetMasterの@mainは外しておきましょう。

現在のファイル構成はこんな感じです。
image.png
ウィジェットとかWatchってTimeLineがあって構成が特殊だから、だいたいいつもこんな感じにまとめてます。
おすすめのまとめ方とかあれば教えてください!

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

ビルドターゲットを「Widget Extension」に変更してアプリをビルドしましょう。
image.png
ウィジェットが表示されれば開発準備OKです!

おわりに

今回はWidgetKitで天気予報アプリ作ってみた~Widget Extension追加編~について書きました。
次回はタイムライン作成の記事を書こうと思っています。

続きが気になる方は↓こちら↓から

ご覧いただきありがとうございました。
こうしたほうがいいや、ここはちょっと違うなど気になる箇所があった場合、ご教示いただけると幸いです。

お知らせ

現在副業でiOSアプリ開発案件を募集しています。
Twitter DMでご依頼お待ちしております!
QR_615427.png
↓活動リンクはこちら↓
https://linktr.ee/sasaki.ken

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?