Widget は、iOSまたはAndroidのホーム画面に表示されるコンパクトなユーザーインターフェース要素で、アプリを開かずに関連情報を表示します。ウィジェットはiOS 14(2020年)でSwiftUI上のWidgetKitフレームワークとともに登場し、Androidではバージョン1.5からKotlinまたはJavaのAppWidgetProviderを通じて存在しています。ユーザーはホーム画面で天気、為替レート、カレンダー、メモ、タスクの状況を直接確認でき、時間を節約し操作を簡素化します。Appleによると、ユーザーは平均して1日4〜6回ウィジェットとやり取りし、ホーム画面にウィジェットを配置するとアプリのエンゲージメントが20〜30%向上します。Androidでも同様の数値です。コード例を交えて、両プラットフォームでのウィジェットの仕組みを見ていきましょう。
重要ポイント
Widget は、モバイルデバイスのホーム画面に配置されるコンパクトなユーザーインターフェース要素で、アプリから関連情報を表示します。フルアプリケーションとは異なり、ウィジェットは起動を必要としません — ユーザーは画面ロック解除後すぐにデータを確認できます。この概念はデスクトップOS(macOS Dashboard、Windows Sidebar)に由来し、モバイル環境ではAndroidが最初のリリース(Android 1.5 Cupcake、2009年)からウィジェット対応を導入しましたが、AppleはiOS 14でようやく追加しました。
技術的実装はプラットフォームによって異なります。iOSウィジェットはXcodeの独立したターゲットで、Widget Extensionとしてコンパイルされ、独自のプロセスで実行されます。AndroidウィジェットはAppWidgetProviderを拡張するBroadcastReceiverで、RemoteViewsを介してレイアウトをレンダリングします。両プラットフォームともパフォーマンスとメモリに厳しい制限を課しており、制限を超えるとシステムがウィジェットを削除する可能性があります。iOSはウィジェットを30〜50MB、タイムライン生成時間を10秒に制限しています。AndroidはupdatePeriodMillisによる更新間隔を最大30分とし、onUpdateがハングすると強制終了します。
Smart Stack(iOS)とスタック(Android)を使用すると、ユーザーは複数のウィジェットを1つのホーム画面スロットにグループ化できます。デバイスは時刻、場所、ユーザーアクティビティに基づいて表示するウィジェットを自動的に切り替えます。例えば、朝はカレンダー、昼は天気、夕方はメモが表示されます。開発者にとって、これはウィジェットがスタックの一部として正しく動作し、常時表示に依存しないことを意味します。
WidgetKit は、iOS 14+、iPadOS 14+、macOS 11+向けのウィジェットを作成するためのAppleのフレームワークです。ウィジェットはSwiftUIとコンテンツ更新のためのTimelineProviderメカニズムを使用してSwiftで記述されます。WidgetKitアーキテクチャには3つのコンポーネントがあります。TimelineProvider — エントリを持つタイムラインを形成するデータソース、Widget Entry View — エントリに基づいてコンテンツを表示するSwiftUI View、Widget Configuration — ウィジェットのタイプ、サイズ、ファミリーの記述です。
WidgetKitは更新を自動的に管理します — 開発者はシステムが許可する以上の頻度でウィジェットを更新することはできません。Appleはデバイス上のすべてのウィジェットに分散される更新予算を使用しています。最小間隔は15〜30分です。重要なデータ(配送状況など)にはpush-to-refreshが使用されます — 関連する将来の日付を含むプッシュ通知を送信し、その後WidgetKitが新しいタイムラインを要求します。iOS 17以降、ウィジェットはApp Intentsによるインタラクティブ性をサポートしています — ユーザーはアプリを開かずにウィジェット内のボタンを押すことができます。
| サイズ | iOS(pt) | コンテンツ |
|---|---|---|
| Small | 170×170 | 1〜2指標: 気温、為替レート |
| Medium | 360×170 | 2〜4指標: 5日間予報、リスト |
| Large | 360×380 | 5〜10指標: カレンダー、ニュースフィード |
AppWidgetProvider は、Androidでウィジェットを作成するための基本クラスで、API level 3からAndroid SDKの一部です。AndroidウィジェットはBroadcastReceiverで、システムから更新、有効化、無効化のイベントを受け取ります。iOSが独自エンジンでSwiftUIを使用してウィジェットをレンダリングするのとは異なり、AndroidはRemoteViews — システムプロセス(ランチャー)でレンダリングされる制限付きのViewコンポーネントセットを使用します。カスタムView、複雑なアニメーション、UIライブラリは使用できません。
Androidウィジェットのワークフローは4つの段階で構成されます。設定 — 画面にウィジェットを追加する際、システムは設定Activityを起動し、ユーザーが設定を選択します。更新 — システムは指定された間隔(最小30分)でonUpdate()を呼び出すか、WorkManagerを介してアプリからのシグナルで呼び出します。レンダリング — プロバイダーが現在のデータでRemoteViewsを作成し、AppWidgetManagerを介して渡します。タッチ処理 — ウィジェットはボタン用のPendingIntentをサポートし、クリックでActivityが開くかアクションが実行されます。Android 12以降、ウィジェットは角丸、アダプティブなMaterial Youカラー、動的サイズ変更をサポートしています。
WidgetKitとSwiftUIを使用して、Swiftで天気ウィジェットを作成しましょう。プロジェクトにはWidget Extensionターゲットを含める必要があり、XcodeでFile → New → Target → Widget Extensionから追加します。
import WidgetKit
import SwiftUI
// 1. ウィジェットのデータモデル
struct WeatherEntry: TimelineEntry {
let date: Date
let temperature: Int
let condition: String
}
// 2. TimelineProvider — データプロバイダー
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> WeatherEntry {
WeatherEntry(date: Date(), temperature: 22, condition: "晴れ")
}
func getSnapshot(in context: Context, completion: @escaping (WeatherEntry) -> Void) {
let entry = WeatherEntry(date: Date(), temperature: 22, condition: "晴れ")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<WeatherEntry>) -> Void) {
let now = Date()
let entry = WeatherEntry(date: now, temperature: 20, condition: "曇り")
let nextUpdate = Calendar.current.date(byAdding: .hour, value: 3, to: now)!
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
}
// 3. 表示用のSwiftUI View
struct WeatherWidgetEntryView: View {
var entry: WeatherEntry
var body: some View {
VStack(alignment: .leading) {
Text("天気").font(.caption).foregroundColor(.secondary)
HStack {
Text("\(entry.temperature)°").font(.largeTitle).fontWeight(.bold)
Spacer()
Text(entry.condition).font(.body)
}
}.padding()
}
}
// 4. ウィジェット設定
@main
struct WeatherWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "WeatherWidget", provider: Provider()) { entry in
WeatherWidgetEntryView(entry: entry)
}
.configurationDisplayName("天気")
.description("現在の気温を表示")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
重要ポイント: TimelineProvider はいつどのデータを表示するかを決定し、getTimeline は.after(nextUpdate)更新ポリシーでタイムラインを作成し、StaticConfiguration はプロバイダーをViewに接続します。動的データには、カスタムパラメーターを持つIntentConfigurationが使用されます。
AppWidgetProviderとRemoteViewsを使用して、Kotlinで同様の天気ウィジェットを実装しましょう。
// WeatherWidgetProvider.kt
class WeatherWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
}
override fun onAppWidgetOptionsChanged(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
newOptions: Bundle
) {
updateAppWidget(context, appWidgetManager, appWidgetId)
}
companion object {
fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
val temperature = 20
val condition = "曇り"
val views = RemoteViews(
context.packageName, R.layout.widget_weather
).apply {
setTextViewText(R.id.tv_temperature, "$temperature°")
setTextViewText(R.id.tv_condition, condition)
setOnClickPendingIntent(
R.id.widget_root, PendingIntent.getActivity(
context, 0,
Intent(context, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
)
}
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
}
// AndroidManifest — プロバイダー登録
<receiver android:name=".WeatherWidgetProvider" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_weather_info" />
</receiver>
widget_weather_info.xmlファイルは最小サイズ、間隔、レイアウトを設定します: minWidth 160dp、minHeight 80dp、updatePeriodMillis 3600000(1時間)。RemoteViewsはサポートされているコンポーネントのみを使用します: LinearLayout、TextView、ImageView。カスタムViewとConstraintLayoutはAndroid 5.0以降で利用可能です。GlanceLayout — RemoteViewsのための実験的なComposeベースのアプローチ — はAndroid 15で登場しました。
よくある質問
iOS WidgetKitは3つの固定サイズ(small、medium、large)とシステム予算によるTimelineProvider更新を提供します。Android AppWidgetProviderは柔軟なセルベースのサイズ、レンダリング用のRemoteViews、updatePeriodMillisまたはWorkManagerによる更新を使用します。
iOSではシステムが更新を制御します — 最小間隔は15〜30分で、重要なデータにはpush-to-refreshが使用されます。Androidでは間隔はupdatePeriodMillis(最小30分)またはバックグラウンドタスク用のWorkManagerで設定されます。
iOSはsmall(170x170pt)、medium(360x170pt)、large(360x380pt)をサポートしています。Androidは柔軟なセルグリッドを使用します: 2x1から5x5まで、サイズはランチャーとOSバージョンに依存します。
iOSではウィジェット内のアニメーションは利用できません — 静的な要素です。Android RemoteViewsはsetFloat()とViewPropertyAnimatorによる基本的なアニメーションをサポートしていますが、複雑なアニメーションは利用できません。
適切に実装すれば — 低下しません。システムはウィジェットのリソースを厳しく制限しています: iOS — 更新とメモリの予算(30〜50MB)、Android — 最小更新間隔。問題のあるウィジェットはシステムによって強制終了されます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。