visionos-widgets について
visionOSの空間環境向けWidgetを開発するためのスキルです。配置、テクスチャ、距離に応じたレイアウト、Widgetファミリー、背景、描画モードを解説します。
主な機能
- 01浮き上がり・埋め込み配置
- 02ガラスと紙のテクスチャ
- 03距離に応じた詳細レベル
- 04空間向けWidgetファミリー
- 05背景と描画モードの扱い
活用シーン
- 01既存のiOS WidgetをvisionOS向けに調整
- 02机上や壁面への配置方法を選択
- 03遠距離でWidgetの内容を簡略化
- 04フルカラーとアクセント付き描画に対応
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rshankras/ claude-code-apple-skills --skill visionos-widgetsインストール後、Claude Code で/visionos-widgetsを入力して読み込まれたか確認します。
詳細
visionOS ウィジェット
visionOS の物理空間に設置するウィジェットを構築するためのパターンです。設置スタイル、テクスチャ、近接に応じた詳細レベル、空間ウィジェットファミリー、レンダリングモードについて説明します。
このスキルが有効になる場合
次のような場合にこのスキルを使用します。
- visionOS 用のウィジェットの作成または移植を依頼された場合
- 設置スタイル(elevated、recessed)について言及された場合
- ウィジェットにガラスまたは紙のテクスチャを設定したい場合
- ウィジェットの近接認識または詳細レベルについて質問された場合
- 空間ウィジェットファミリーまたは
.systemExtraLargePortraitについて言及された場合 - コンテナの背景またはレンダリングモード(フルカラーとアクセント付き)を制御したい場合
- 既存の iOS/iPadOS ウィジェットを visionOS に移植する場合
判断ツリー
What do you need for your visionOS widget?
|
+- Where should the widget appear?
| +- On a surface (table, shelf) -> .elevated (default)
| +- Embedded in a wall -> .recessed
| +- Both -> .supportedMountingStyles([.elevated, .recessed])
|
+- What visual treatment?
| +- Transparent, blends with environment -> .glass (default)
| +- Opaque, poster-like appearance -> .paper
|
+- How should it respond to user distance?
| +- Full detail when close -> @Environment(\.levelOfDetail) == .default
| +- Simplified when far -> @Environment(\.levelOfDetail) == .simplified
|
+- What size families?
| +- Standard -> .systemSmall, .systemMedium, .systemLarge, .systemExtraLarge
| +- Tall portrait -> .systemExtraLargePortrait (visionOS only)
|
+- How should colors render?
| +- Full color (default) -> No extra work
| +- System-tinted monochrome -> Mark backgrounds with .containerBackground(for:)
API の利用可能状況
| API | 最小バージョン | 備考 |
|---|---|---|
| visionOS の WidgetKit | visionOS 1.0 | 基本的なウィジェットのサポート |
.containerBackground(for: .widget) | visionOS 1.0 | 削除可能な背景の指定 |
@Environment(\.showsWidgetContainerBackground) | visionOS 1.0 | 背景の表示状態を確認 |
.supportedMountingStyles() | visionOS 2.0 | elevated と recessed の配置 |
.widgetTexture(.glass / .paper) | visionOS 2.0 | ウィジェットの表面素材 |
@Environment(\.levelOfDetail) | visionOS 2.0 | 近接に応じたレイアウト |
.systemExtraLargePortrait | visionOS 2.0 | 縦長のポートレートウィジェットファミリー |
ウィジェットの完全な例
この例では、設置スタイル、テクスチャ、ファミリー、近接認識を組み合わせて示します。
struct MyWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "com.example.mywidget",
provider: Provider()
) { entry in
MyWidgetView(entry: entry)
}
.supportedFamilies([
.systemSmall, .systemMedium, .systemLarge,
.systemExtraLarge, .systemExtraLargePortrait
])
.supportedMountingStyles([.elevated, .recessed])
.widgetTexture(.glass) // .glass is default, .paper for opaque
}
}
設置スタイル:.elevated(デフォルト)は、テーブルなどの表面に置かれます。.recessed は、額装された絵のように壁に埋め込まれます。.supportedMountingStyles() を省略すると、elevated のみが使用されます。recessed は垂直面でのみ機能し、水平面への配置は常に elevated になります。水平面では、システムがユーザー側に向けて緩やかな傾きを加えます。その角度に合わせてデザインし、無理に逆らわないようにしてください。
テクスチャ:.glass(デフォルト)は周囲の環境になじみます。.paper は不透明で、豊かな画像を使う場合に適しています。
空間での動作(WWDC25)
ウィジェットは物理面にのみ設置できる常設の部屋の設備です。セッション、部屋の変更、電源の入れ直しをまたいで維持され、同じ部屋に複数のインスタンスを共存させられます。
- ユーザーによるサイズ変更:角の操作部分を使って、ウィジェットのサイズをテンプレートの 75% から 125% まで変更できます。レイアウトはこの範囲全体に対応する必要があります。
- フレーム:ユーザーはテンプレートのサイズとは独立して、5 種類のフレーム幅(薄いものから厚いものまで)を選択します。recessed スタイルではフレーム幅が固定されます。
- フレームの色合い:ウィジェットのフレームには常にユーザーの色合いが適用され、無効にはできません。背景で色合いの適用を無効にしている場合でも、前景要素はシステムの7 種類のライトパレットと 7 種類のダークパレットすべてで適切に表示される必要があります。
- アセット:ウィジェットは実物大でレンダリングされるため、近距離でも画像が鮮明に見えるよう、高解像度のアセットを同梱してください。
近接認識(詳細レベル)
システムはユーザーとの距離に応じて、アニメーションを伴いながら .default(近距離)と .simplified(遠距離)を自動的に切り替えます。情報の密度を下げ、重要な情報を大きく表示して簡略化してください。
struct MyWidgetView: View {
let entry: Provider.Entry
@Environment(\.levelOfDetail) private var levelOfDetail
var body: some View {
switch levelOfDetail {
case .default:
VStack(alignment: .leading, spacing: 8) {
Text(entry.title).font(.headline)
Text(entry.subtitle).font(.subheadline).foregroundStyle(.secondary)
DetailChart(data: entry.chartData)
}
.padding()
case .simplified:
VStack(spacing: 4) {
Image(systemName: entry.iconName).font(.largeTitle)
Text(entry.title).font(.headline)
}
.padding()
@unknown default:
Text(entry.title).padding()
}
}
}
将来の互換性のため、必ず @unknown default を処理してください。
ウィジェットファミリー
| ファミリー | 説明 |
|---|---|
.systemSmall | コンパクトな正方形 — ひと目で確認できる情報 |
.systemMedium | 横長の長方形 — 2 列表示またはリストのプレビュー |
.systemLarge | 大きな正方形 — グラフや詳細なコンテンツ |
.systemExtraLarge | 特大の横長 — ダッシュボード |
.systemExtraLargePortrait | 特大の縦長 — visionOS 専用。壁のアートのような「存在感のある」ウィジェット |
マルチプラットフォームのターゲットでは、visionOS 専用ファミリーを条件付きで使用してください。
.supportedFamilies({
var families: [WidgetFamily] = [.systemSmall, .systemMedium, .systemLarge]
#if os(visionOS)
families.append(.systemExtraLargePortrait)
#endif
return families
}())
コンテナの背景とレンダリングモード
アクセント付きレンダリングモードでは、システムが背景を削除し、色合いを適用します。どちらのモードでもウィジェットが正しくレンダリングされるよう、削除可能な背景を指定してください。
struct MyWidgetView: View {
let entry: Provider.Entry
@Environment(\.showsWidgetContainerBackground) var showsBackground
var body: some View {
VStack {
Image(systemName: "star.fill").font(.largeTitle)
Text(entry.title)
.font(.headline)
.foregroundStyle(showsBackground ? .white : .primary)
}
.padding()
.containerBackground(for: .widget) {
LinearGradient(
colors: [.blue, .purple],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
}
}
}
- フルカラー(デフォルト):すべての色がそのままレンダリングされます。
- アクセント付き:コンテナの背景が削除され、システムが単色の色合いを適用します。
visionOS ウィジェットのプレビュー
#Preview("Close Up", as: .systemSmall) {
MyWidget()
} timelineProvider: {
Provider()
}
#Preview("Extra Large Portrait", as: .systemExtraLargePortrait) {
MyWidget()
} timelineProvider: {
Provider()
}
よくある間違いトップ 5
| # | 間違い | 修正方法 |
|---|---|---|
| 1 | .containerBackground(for: .widget) がない — アクセントモードで何も表示されない | 背景は必ず .containerBackground(for: .widget) { } で囲みます |
| 2 | levelOfDetail を無視する — 詳細なビューは部屋の反対側から読めない | テキストを大きくし、要素を減らした .simplified レイアウトを用意します |
| 3 | iOS で .systemExtraLargePortrait を使う — ビルドエラーまたは実行時クラッシュが発生する | #if os(visionOS) または visionOS 専用ターゲットで囲みます |
| 4 | ガラスのテクスチャと合わない色をハードコードする | .foregroundStyle(.primary / .secondary) とシステムカラーを使います |
| 5 | levelOfDetail の switch に @unknown default がない | 将来の互換性のため、必ず含めます |
アンチパターン
// ❌ No container background — accented mode shows nothing
struct BadWidgetView: View {
var body: some View {
ZStack {
Color.blue // Not marked as removable
Text("Hello")
}
}
}
// ✅ Background marked as removable
struct GoodWidgetView: View {
var body: some View {
Text("Hello")
.containerBackground(for: .widget) { Color.blue }
}
}
// ❌ Same complex layout at all distances
struct BadProximityView: View {
var body: some View {
VStack {
Text(entry.title).font(.caption2) // Unreadable far away
DetailChart(data: entry.data)
}
}
}
// ✅ Simplified layout when far away
struct GoodProximityView: View {
@Environment(\.levelOfDetail) private var levelOfDetail
var body: some View {
switch levelOfDetail {
case .default: DetailedLayout(entry: entry)
case .simplified: SimplifiedLayout(entry: entry)
@unknown default: SimplifiedLayout(entry: entry)
}
}
}
レビューチェックリスト
マウントとテクスチャ
- ウィジェットを奥まって表示する場合や、両方の表示に対応する場合は、マウントスタイルを明示的に設定している
- 豊富な画像を含むウィジェットでは、テクスチャを
.paperに設定している - ウィジェットを浮き出した配置と奥まった配置の両方でテストしている(両方に対応する場合)
- テンプレートサイズの 75% から 125% までユーザーがサイズを変更しても、レイアウトが崩れない
距離の認識
-
@Environment(\.levelOfDetail)により、離れた位置から見る場合の簡略化されたレイアウトを提供している -
.simplifiedレイアウトでは、テキストを大きくし、要素を減らし、コントラストの高いビジュアルを使用している -
levelOfDetailの switch に@unknown defaultケースがある
ファミリーとレイアウト
- マルチプラットフォームターゲットでは
.systemExtraLargePortraitを#if os(visionOS)で囲んでいる - ウィジェットのコンテンツが、サポートする各ファミリーサイズに適応している
- Xcode のプレビューを使って、宣言したすべてのファミリーサイズでレイアウトをテストしている
背景とレンダリング
- 削除可能な背景を示すために
.containerBackground(for: .widget) { }を使っている - フルカラーとアクセントモードの両方で、ウィジェットが正しく描画される
- 前景色が背景に依存する場合、
showsWidgetContainerBackgroundを確認している - ガラスのテクスチャとの互換性のために、システムのセマンティックカラーを使っている
- 7 種類すべてのライトフレームティントパレットと、7 種類すべてのダークフレームティントパレットで、前景が読みやすい