はじめに
ウィジェットの技術検証とキャッチアップを兼ねて社内で勉強会を開催しました。
今回は、WidgetExtensionを追加してWidgetFamilyごとに表示するところまでを記事にします。
環境
Swift 5.5
Xcode 13.1
サンプルプロジェクト
GitHubにPushしました。気になる方はご覧ください。
https://github.com/ken-sasaki-222/WidgetTraining
ウィジェットをローカルでビルドする
ウィジェット機能を利用したいアプリの作成
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
を確認しましょう。
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を以下のように編集します。
/// 省略///
@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の追加
systemSmall
、systemMedium
、systemLarge
、3種類のWidgetFamilyを追加します。
///省略///
@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()
)をテキストに変更してみようと思います。
WidgetEntryView
にEnvironmentObject
を追加して、WidgetFamilyごとにテキストを返します。
///省略///
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も以下のように書き換えます。
///省略///
@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
を編集します。
///省略///
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ホーム画面で
アプリ名を入力してウィジェットを検索
このように、systemSmall
,systemMedium
,systemLarge
が表示されれば成功です。
おわりに
今回はWidgetExtensionを追加してWidgetFamilyごとに表示する方法を書きました。
ウィジェットはあまりキャッチアップできてなかったので良い機会になりました。
引き続きサンプルプロジェクトを肥やしていこうと思います。
ご覧いただきありがとうございました。
ここはこうしたほうがいい、ここはちょっと違うなどありましたらご教示いただければ幸いです