visionos-widgets

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

visionOS Widgets

visionOS 小组件

Patterns for building widgets that live in physical space on visionOS. Covers mounting styles, textures, proximity-aware detail levels, spatial widget families, and rendering modes.
本内容介绍了在visionOS的物理空间中构建小组件的设计模式,涵盖挂载样式、纹理、近距感知细节层级、空间小组件系列以及渲染模式。

When This Skill Activates

本技能适用场景

Use this skill when the user:
  • Asks to create or adapt a widget for visionOS
  • Mentions mounting styles (elevated, recessed)
  • Wants glass or paper texture on a widget
  • Asks about proximity awareness or level of detail in widgets
  • Mentions spatial widget families or
    .systemExtraLargePortrait
  • Wants to control container backgrounds or rendering modes (full color vs accented)
  • Is porting an existing iOS/iPadOS widget to visionOS
当用户有以下需求时,可使用本技能:
  • 请求创建或适配visionOS 小组件
  • 提及挂载样式(elevated、recessed)
  • 希望为小组件添加玻璃或纸张纹理
  • 询问小组件的近距感知或细节层级相关内容
  • 提及空间小组件系列或
    .systemExtraLargePortrait
  • 想要控制容器背景或渲染模式(全彩 vs 强调色)
  • 正在将现有iOS/iPadOS小组件移植到visionOS

Decision Tree

决策树

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:)
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 Availability

API 可用性

APIMinimum VersionNotes
WidgetKit on visionOSvisionOS 1.0Basic widget support
.containerBackground(for: .widget)
visionOS 1.0Removable background marking
@Environment(\.showsWidgetContainerBackground)
visionOS 1.0Background visibility check
.supportedMountingStyles()
visionOS 2.0Elevated and recessed placement
.widgetTexture(.glass / .paper)
visionOS 2.0Widget surface material
@Environment(\.levelOfDetail)
visionOS 2.0Proximity-aware layouts
.systemExtraLargePortrait
visionOS 2.0Tall portrait widget family
API最低版本说明
WidgetKit on visionOSvisionOS 1.0基础小组件支持
.containerBackground(for: .widget)
visionOS 1.0可移除背景标记
@Environment(\.showsWidgetContainerBackground)
visionOS 1.0背景可见性检查
.supportedMountingStyles()
visionOS 2.0支持悬浮和嵌入两种放置方式
.widgetTexture(.glass / .paper)
visionOS 2.0小组件表面材质
@Environment(\.levelOfDetail)
visionOS 2.0近距感知布局
.systemExtraLargePortrait
visionOS 2.0竖屏超大尺寸小组件系列

Complete Widget Example

完整小组件示例

This example demonstrates mounting styles, textures, families, and proximity awareness together:
swift
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
    }
}
Mounting styles:
.elevated
(default) sits on surfaces like tables.
.recessed
embeds into walls like a framed picture. Omit
.supportedMountingStyles()
to use elevated only.
Textures:
.glass
(default) is transparent and blends with the environment.
.paper
is opaque and poster-like, best for rich imagery.
以下示例展示了挂载样式、纹理、尺寸系列和近距感知的综合用法:
swift
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 是默认值,.paper 为不透明材质
    }
}
挂载样式:
.elevated
(默认)用于放置在桌面、架子等平面上。
.recessed
用于嵌入墙面,类似挂画效果。省略
.supportedMountingStyles()
则仅使用悬浮样式。
纹理:
.glass
(默认)为透明材质,可与环境融合。
.paper
为不透明的海报样式,适合展示丰富图像。

Proximity Awareness (Level of Detail)

近距感知(细节层级)

The system tracks user distance and transitions between detail levels automatically with animation.
swift
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()
        }
    }
}
Always handle
@unknown default
for forward compatibility.
系统会自动跟踪用户距离,并通过动画在不同细节层级间切换。
swift
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
分支以保证向前兼容性。

Widget Families

小组件尺寸系列

FamilyDescription
.systemSmall
Compact square -- glanceable info
.systemMedium
Wide rectangle -- two-column or list preview
.systemLarge
Large square -- charts, detailed content
.systemExtraLarge
Extra-large landscape -- dashboards
.systemExtraLargePortrait
Extra-large portrait -- visionOS only
Guard the visionOS-only family in multiplatform targets:
swift
.supportedFamilies({
    var families: [WidgetFamily] = [.systemSmall, .systemMedium, .systemLarge]
    #if os(visionOS)
    families.append(.systemExtraLargePortrait)
    #endif
    return families
}())
系列描述
.systemSmall
紧凑正方形——用于快速查看信息
.systemMedium
宽矩形——双列或列表预览
.systemLarge
大正方形——展示图表、详细内容
.systemExtraLarge
横屏超大尺寸——仪表盘类内容
.systemExtraLargePortrait
竖屏超大尺寸——仅visionOS支持
在多平台项目中需对visionOS专属尺寸系列做防护处理:
swift
.supportedFamilies({
    var families: [WidgetFamily] = [.systemSmall, .systemMedium, .systemLarge]
    #if os(visionOS)
    families.append(.systemExtraLargePortrait)
    #endif
    return families
}())

Container Backgrounds and Rendering Modes

容器背景与渲染模式

In accented rendering mode, the system removes backgrounds and applies a tint color. Mark removable backgrounds so the widget renders correctly in both modes.
swift
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
            )
        }
    }
}
  • Full color (default): All colors render intact.
  • Accented: Container background is removed; system applies a monochrome tint.
在强调色渲染模式下,系统会移除背景并应用色调颜色。需标记可移除背景,确保小组件在两种模式下都能正确显示。
swift
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
            )
        }
    }
}
  • 全彩模式(默认):所有颜色正常显示。
  • 强调色模式:容器背景被移除,系统应用单色色调。

Previewing visionOS Widgets

预览visionOS小组件

swift
#Preview("Close Up", as: .systemSmall) {
    MyWidget()
} timelineProvider: {
    Provider()
}

#Preview("Extra Large Portrait", as: .systemExtraLargePortrait) {
    MyWidget()
} timelineProvider: {
    Provider()
}
swift
#Preview("Close Up", as: .systemSmall) {
    MyWidget()
} timelineProvider: {
    Provider()
}

#Preview("Extra Large Portrait", as: .systemExtraLargePortrait) {
    MyWidget()
} timelineProvider: {
    Provider()
}

Top 5 Mistakes

五大常见错误

#MistakeFix
1Missing
.containerBackground(for: .widget)
-- accented mode renders blank
Always wrap backgrounds in
.containerBackground(for: .widget) { }
2Ignoring
levelOfDetail
-- detailed views unreadable from across the room
Provide a
.simplified
layout with larger text, fewer elements
3Using
.systemExtraLargePortrait
on iOS -- build error or runtime crash
Guard with
#if os(visionOS)
or visionOS-only targets
4Hardcoding colors that clash with glass textureUse
.foregroundStyle(.primary / .secondary)
and system colors
5No
@unknown default
in
levelOfDetail
switch
Always include for forward compatibility
序号错误修复方案
1缺少
.containerBackground(for: .widget)
——强调色模式下显示空白
始终使用
.containerBackground(for: .widget) { }
包裹背景
2忽略
levelOfDetail
——远距离时详细视图无法阅读
提供
.simplified
布局,使用更大字体、更少元素
3在iOS上使用
.systemExtraLargePortrait
——编译错误或运行时崩溃
使用
#if os(visionOS)
做防护,或仅在visionOS专属目标中使用
4硬编码与玻璃纹理冲突的颜色使用
.foregroundStyle(.primary / .secondary)
和系统颜色
5
levelOfDetail
分支中缺少
@unknown default
务必添加该分支以保证向前兼容性

Anti-Patterns

反模式示例

swift
// ❌ 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 }
    }
}
swift
// ❌ 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)
        }
    }
}
swift
// ❌ 无容器背景——强调色模式下无内容显示
struct BadWidgetView: View {
    var body: some View {
        ZStack {
            Color.blue  // 未标记为可移除
            Text("Hello")
        }
    }
}

// ✅ 背景标记为可移除
struct GoodWidgetView: View {
    var body: some View {
        Text("Hello")
            .containerBackground(for: .widget) { Color.blue }
    }
}
swift
// ❌ 所有距离下使用相同复杂布局
struct BadProximityView: View {
    var body: some View {
        VStack {
            Text(entry.title).font(.caption2)  // 远距离无法阅读
            DetailChart(data: entry.data)
        }
    }
}

// ✅ 远距离时使用简化布局
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)
        }
    }
}

Review Checklist

检查清单

Mounting and Texture

挂载与纹理

  • Mounting style explicitly set if widget should appear recessed or support both
  • Texture set to
    .paper
    for widgets with rich imagery
  • Widget tested in both elevated and recessed placements (if both supported)
  • 若小组件需嵌入墙面或同时支持两种放置方式,需显式设置挂载样式
  • 含丰富图像的小组件需将纹理设置为
    .paper
  • 若支持两种挂载方式,需在两种场景下测试小组件

Proximity Awareness

近距感知

  • @Environment(\.levelOfDetail)
    provides simplified layout for distant viewers
  • .simplified
    layout uses larger text, fewer elements, high-contrast visuals
  • @unknown default
    case present in
    levelOfDetail
    switch
  • 使用
    @Environment(\.levelOfDetail)
    为远距离用户提供简化布局
  • .simplified
    布局使用更大字体、更少元素、高对比度视觉效果
  • levelOfDetail
    分支中包含
    @unknown default
    情况

Families and Layout

尺寸系列与布局

  • .systemExtraLargePortrait
    guarded with
    #if os(visionOS)
    in multiplatform targets
  • Widget content adapts to each supported family size
  • Layout tested in all declared family sizes via Xcode previews
  • 在多平台项目中,
    .systemExtraLargePortrait
    需用
    #if os(visionOS)
    做防护
  • 小组件内容适配所有支持的尺寸系列
  • 通过Xcode预览在所有声明的尺寸系列中测试布局

Backgrounds and Rendering

背景与渲染

  • .containerBackground(for: .widget) { }
    used to mark removable backgrounds
  • Widget renders correctly in both full color and accented modes
  • showsWidgetContainerBackground
    checked if foreground colors depend on background
  • System semantic colors used for glass texture compatibility
  • 使用
    .containerBackground(for: .widget) { }
    标记可移除背景
  • 小组件在全彩和强调色模式下均可正确渲染
  • 若前景颜色依赖背景,需检查
    showsWidgetContainerBackground
    状态
  • 使用系统语义颜色以兼容玻璃纹理

References

参考资料