widgetkit について
Apple プラットフォーム向けに WidgetKit を開発するためのスキルです。ウィジェットのレイアウト、描画モード、操作、設定、プラットフォームごとの制約を扱います。
主な機能
- 01ウィジェット構造とタイムライン
- 02ウィジェットファミリーへの適応
- 03描画モードへの対応
- 04ボタン、トグル、ディープリンク
- 05設定タイプと制約
活用シーン
- 01ホーム画面やロック画面のウィジェット作成
- 02サイズに応じたレイアウト設計
- 03ウィジェット操作とアプリへのリンク追加
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ ios-agent/ iosagent.dev --skill widgetkitインストール後、Claude Code で/widgetkitを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/design-guidelines.mddimensions.mdinteractivity.mdlive-activities.mdplatforms.mdtimelines.md詳細
WidgetKit 開発スキル
WidgetKit を使用して、Apple の各プラットフォームで一目でわかり、タイムリーな体験を構築します。
クイックスタート
ウィジェット拡張機能を作成する
- File → New → Target → Widget Extension
- 静的ウィジェットの場合は、「Include Live Activity」と「Include Configuration App Intent」の選択を解除します
- ウィジェットには
Widgetプロトコル、TimelineProvider、SwiftUI ビューが必要です
最小限のウィジェット構造
@main
struct MyWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.app.mywidget",
provider: Provider()
) { entry in
MyWidgetView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("Shows key information")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: .now)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
completion(SimpleEntry(date: .now))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
let entry = SimpleEntry(date: .now)
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: .now)!
completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
}
ウィジェットファミリーとサイズ
| ファミリー | プラットフォーム | 用途 |
|---|---|---|
systemSmall | iOS、iPadOS、macOS、visionOS | 単一のタップ対象、一目でわかる情報 |
systemMedium | iOS、iPadOS、macOS、visionOS | 複数のデータポイント、インタラクティブな要素 |
systemLarge | iOS、iPadOS、macOS、visionOS | 豊富なコンテンツ、複数のインタラクション |
systemExtraLarge | iPadOS、macOS、visionOS | ダッシュボード形式のレイアウト |
accessoryCircular | iOS ロック画面、watchOS | 最小限の情報、ゲージ形式 |
accessoryRectangular | iOS ロック画面、watchOS | 2~3 行のテキスト |
accessoryInline | iOS ロック画面、watchOS | 1 行のテキスト+任意の画像 |
accessoryCorner | watchOS のみ | コーナーコンプリケーション |
ウィジェットファミリーに合わせる
struct MyWidgetView: View {
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .systemSmall: CompactView()
case .systemMedium: MediumView()
case .systemLarge: DetailedView()
case .accessoryCircular: GaugeView()
case .accessoryRectangular: RectangularView()
default: CompactView()
}
}
}
レンダリングモード
ウィジェットはコンテキストに応じて異なる方法でレンダリングされます。
| モード | 使用時 | 動作 |
|---|---|---|
fullColor | ホーム画面(iOS 17-)、macOS デスクトップ | フルカラーを維持 |
accented | ホーム画面の色付き/透明表示、visionOS、watchOS | アクセントグループとプライマリーグループに分割 |
vibrant | ロック画面、スタンバイ | 彩度を下げ、ぼかし効果を適用 |
@Environment(\.widgetRenderingMode) var renderingMode
var body: some View {
switch renderingMode {
case .fullColor: FullColorView()
case .accented: AccentedView()
case .vibrant: VibrantView()
@unknown default: FullColorView()
}
}
インタラクティブ機能
ボタンとトグル(iOS 17 以降)
Button(intent: RefreshIntent()) {
Label("Refresh", systemImage: "arrow.clockwise")
}
Toggle(isOn: $isEnabled, intent: ToggleIntent()) {
Text("Enable")
}
ディープリンク
MyWidgetView()
.widgetURL(URL(string: "myapp://detail/123")!)
// Or for multiple links in larger widgets:
Link(destination: URL(string: "myapp://item/1")!) {
ItemView()
}
構成タイプ
| タイプ | 用途 |
|---|---|
StaticConfiguration | ユーザーによる構成が不要 |
AppIntentConfiguration | ユーザーが構成可能(iOS 17 以降) |
ActivityConfiguration | ライブアクティビティ |
リファレンスドキュメント
- タイムラインと更新:タイムラインプロバイダー、再読み込みポリシー、プッシュ更新
- インタラクティブ機能:ボタン、トグル、App Intents、ディープリンク
- ライブアクティビティ:ActivityKit、Dynamic Island、ロック画面
- デザインガイドライン:HIG のベストプラクティス、レイアウト、タイポグラフィ
- プラットフォーム固有の情報:iOS、watchOS、macOS、visionOS の違い
- 寸法:デバイスごとのすべてのウィジェットファミリーの正確なサイズ
主な制約
- リアルタイム更新は不可:タイムラインを使用します。システムは更新をまとめて処理します
- 予算に制限あり:使用状況に応じて 1 日あたり約 40~70 回更新されます
- 連続アニメーションは不可:最大 2 秒のトランジションアニメーションのみ使用できます
- SwiftUI のみ:UIKit ビューはサポートされません
- ステートレス:レンダリング間で永続的な状態は保持されません
- ビューでのネットワーク通信は不可:データの取得はタイムラインプロバイダーでのみ行います
よく使われるパターン
メインアプリとデータを共有する
// Use App Groups
let sharedDefaults = UserDefaults(suiteName: "group.com.app.shared")
// Or shared container
let containerURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.app.shared"
)
ウィジェットを強制的に更新する
import WidgetKit
WidgetCenter.shared.reloadTimelines(ofKind: "com.app.mywidget")
WidgetCenter.shared.reloadAllTimelines()
機密コンテンツのプレースホルダー
.privacySensitive() // Redacts when device locked
コメント
Markdown 対応