axiom-swiftui-26-ref

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SwiftUI 26 Features

SwiftUI 26 新特性

Overview

概述

Comprehensive guide to new SwiftUI features in iOS 26, iPadOS 26, macOS Tahoe, watchOS 26, and visionOS 26. From the Liquid Glass design system to rich text editing, these enhancements make SwiftUI more powerful across all Apple platforms.
Core principle From low level performance improvements all the way up through the buttons in your user interface, there are some major improvements across the system.
本指南全面介绍iOS 26、iPadOS 26、macOS Tahoe、watchOS 26和visionOS 26中的SwiftUI新特性。从Liquid Glass设计系统到富文本编辑,这些增强功能让SwiftUI在所有Apple平台上更加强大。
核心原则 从底层性能优化到用户界面中的按钮,整个系统都有重大改进。

When to Use This Skill

何时使用本技能

  • Adopting the Liquid Glass design system
  • Implementing rich text editing with AttributedString
  • Embedding web content with WebView
  • Optimizing list and scrolling performance
  • Using the @Animatable macro for custom animations
  • Building 3D spatial layouts on visionOS
  • Bridging SwiftUI scenes to UIKit/AppKit apps
  • Implementing drag and drop with multiple items
  • Creating 3D charts with Chart3D
  • Adding widgets to visionOS or CarPlay
  • Adding custom tick marks to sliders (chapter markers, value indicators)
  • Constraining slider selection ranges with
    enabledBounds
  • Customizing slider appearance (thumb visibility, current value labels)
  • Creating sticky safe area bars with blur effects
  • Opening URLs in in-app browser
  • Using system-styled close and confirm buttons
  • Applying glass button styles (iOS 26.1+)
  • Controlling button sizing behavior
  • Implementing compact search toolbars
  • 采用Liquid Glass设计系统
  • 使用AttributedString实现富文本编辑
  • 嵌入WebView加载网页内容
  • 优化列表与滚动性能
  • 使用@Animatable宏实现自定义动画
  • 在visionOS上构建3D空间布局
  • 将SwiftUI场景桥接到UIKit/AppKit应用
  • 实现多项目拖放功能
  • 使用Chart3D创建3D图表
  • 为visionOS或CarPlay添加小组件
  • 为滑块添加自定义刻度标记(章节标记、值指示器)
  • 使用
    enabledBounds
    约束滑块选择范围
  • 自定义滑块外观(滑块 thumb 可见性、当前值标签)
  • 创建带模糊效果的粘性安全区域栏
  • 在应用内浏览器中打开URL
  • 使用系统样式的关闭和确认按钮
  • 应用玻璃按钮样式(iOS 26.1+)
  • 控制按钮尺寸行为
  • 实现紧凑搜索工具栏

System Requirements

系统要求

iOS 26+, iPadOS 26+, macOS Tahoe+, watchOS 26+, visionOS 26+

iOS 26+, iPadOS 26+, macOS Tahoe+, watchOS 26+, visionOS 26+



Liquid Glass Design System

Liquid Glass设计系统

For comprehensive Liquid Glass coverage, see

如需全面了解Liquid Glass,请参阅

  • axiom-liquid-glass
    skill — Design principles, implementation, variants, design review pressure
  • axiom-liquid-glass-ref
    skill — App-wide adoption guide (app icons, controls, navigation, menus, windows)
  • axiom-liquid-glass
    技能——设计原则、实现方式、变体、设计评审要点
  • axiom-liquid-glass-ref
    技能——全应用采用指南(应用图标、控件、导航、菜单、窗口)

Overview

概述

The new design system provides "a bright and fluid experience that's consistent across Apple platforms." Apps automatically adopt the new appearance upon recompilation - navigation containers, tab bars, and toolbars update automatically.
新设计系统提供“在所有Apple平台上一致的明亮流畅体验”。应用重新编译后会自动采用新外观——导航容器、标签栏和工具栏会自动更新。

Key visual elements

关键视觉元素

  • Glassy sidebars on iPad/macOS that reflect surrounding content
  • Compact tab bars on iPhone
  • Liquid Glass toolbar items with morphing transitions
  • Blur effects on scroll edges
  • iPad/macOS上的玻璃质感侧边栏,可反射周围内容
  • iPhone上的紧凑标签栏
  • 带有变形过渡效果的Liquid Glass工具栏项
  • 滚动边缘的模糊效果

Automatic Adoption

自动采用

swift
// No code changes required - recompile and get new design
NavigationSplitView {
    List {
        // Sidebar automatically gets glassy appearance on iPad/macOS
    }
} detail: {
    // Detail view
}

// Tab bars automatically compact on iPhone
TabView {
    // Tabs get new appearance
}
swift
// 无需修改代码——重新编译即可获得新设计
NavigationSplitView {
    List {
        // 侧边栏在iPad/macOS上自动获得玻璃质感外观
    }
} detail: {
    // 详情视图
}

// iPhone上的标签栏自动变为紧凑样式
TabView {
    // 标签页获得新外观
}

Toolbar Customization

工具栏自定义

Toolbar Spacer API

工具栏间隔器API

swift
.toolbar {
    ToolbarItemGroup(placement: .topBarTrailing) {
        Button("Up") { }
        Button("Down") { }

        // Fixed spacer separates button groups
        ToolbarSpacer(.fixed)

        Button("Settings") { }
    }
}
swift
.toolbar {
    ToolbarItemGroup(placement: .topBarTrailing) {
        Button("Up") { }
        Button("Down") { }

        // 固定间隔器分隔按钮组
        ToolbarSpacer(.fixed)

        Button("Settings") { }
    }
}

ToolbarItemGroup for Visual Grouping

用于视觉分组的ToolbarItemGroup

Items within a
ToolbarItemGroup
share a single Liquid Glass background, creating a visual "pill" for related actions:
swift
.toolbar {
    ToolbarItem(placement: .cancellationAction) {
        Button("Cancel", systemImage: "xmark") {}
    }

    ToolbarItemGroup(placement: .primaryAction) {
        Button("Draw", systemImage: "pencil") {}
        Button("Erase", systemImage: "eraser") {}
    }

    ToolbarSpacer(.flexible)

    ToolbarItem(placement: .confirmationAction) {
        Button("Save", systemImage: "checkmark") {}
    }
}
Key insight:
ToolbarItemPlacement
now controls visual appearance, not just position.
confirmationAction
automatically applies
glassProminent
styling;
cancellationAction
uses standard glass.
ToolbarItemGroup
内的项共享同一个Liquid Glass背景,为相关操作创建视觉上的“胶囊”效果:
swift
.toolbar {
    ToolbarItem(placement: .cancellationAction) {
        Button("Cancel", systemImage: "xmark") {}
    }

    ToolbarItemGroup(placement: .primaryAction) {
        Button("Draw", systemImage: "pencil") {}
        Button("Erase", systemImage: "eraser") {}
    }

    ToolbarSpacer(.flexible)

    ToolbarItem(placement: .confirmationAction) {
        Button("Save", systemImage: "checkmark") {}
    }
}
核心要点
ToolbarItemPlacement
现在控制视觉外观,而不仅仅是位置。
confirmationAction
会自动应用
glassProminent
样式;
cancellationAction
使用标准玻璃样式。

Prominent Tinted Buttons in Liquid Glass

Liquid Glass中的突出着色按钮

swift
Button("Add Trip") {
    addTrip()
}
.buttonStyle(.borderedProminent)
.tint(.blue)
// Liquid Glass toolbars support tinting for prominence
Toolbar items also support
.badge()
for notification counts:
swift
ToolbarItem(placement: .confirmationAction) {
    Button("Done", systemImage: "checkmark") { }
        .badge(3)  // Badge count on glass toolbar item
}
swift
Button("Add Trip") {
    addTrip()
}
.buttonStyle(.borderedProminent)
.tint(.blue)
// Liquid Glass工具栏支持着色以突出显示
工具栏项还支持
.badge()
显示通知计数:
swift
ToolbarItem(placement: .confirmationAction) {
    Button("Done", systemImage: "checkmark") { }
        .badge(3)  // 玻璃工具栏项上的徽章计数
}

Removing Items from Group Background

从组背景中移除项

Some toolbar items should appear without the shared group background, like an avatar or standalone icon. Apply
sharedBackgroundVisibility(.hidden)
to separate an item visually:
swift
.toolbar {
    ToolbarItem(placement: .topBarTrailing) {
        // Avatar appears without glass background pill
        Image(systemName: "person.crop.circle")
            .sharedBackgroundVisibility(.hidden)
    }
}
某些工具栏项不应显示共享组背景,例如头像或独立图标。应用
sharedBackgroundVisibility(.hidden)
可在视觉上分离项:
swift
.toolbar {
    ToolbarItem(placement: .topBarTrailing) {
        // 头像显示时无玻璃背景胶囊
        Image(systemName: "person.crop.circle")
            .sharedBackgroundVisibility(.hidden)
    }
}

Monochrome Icon Rendering

单色图标渲染

Icons use monochrome rendering in more places, including toolbars. This reduces visual noise, emphasizes content, and maintains legibility. You can still tint icons with
.tint()
, but use it to convey meaning (call to action, next step) — not just for visual effect.
图标在更多地方使用单色渲染,包括工具栏。这减少了视觉噪音,突出了内容,并保持了可读性。你仍然可以使用
.tint()
为图标着色,但应仅用于传达含义(调用操作、下一步)——而不仅仅是视觉效果。

Toolbar Transitions & Morphing

工具栏过渡与变形

iOS 26 toolbars automatically morph between screens during NavigationStack push/pop transitions. The key insight: attach
.toolbar {}
to individual views inside NavigationStack, not to NavigationStack itself.
iOS 26工具栏在NavigationStack推送/弹出界面时会自动在屏幕间变形。核心要点:将
.toolbar {}
附加到NavigationStack内的各个视图,而不是NavigationStack本身。

DefaultToolbarItem

DefaultToolbarItem

iOS 26+. A toolbar item representing a system component. Use it to reposition system-provided items (like search) within your toolbar layout.
swift
struct DefaultToolbarItem {
    init(kind: ToolbarDefaultItemKind, placement: ToolbarItemPlacement = .automatic)
}
Key semantic: If the system has already placed a matching item
kind
in the toolbar,
DefaultToolbarItem
implicitly replaces the default-placed instance. This lets you move system items to different placements or reposition them relative to your own toolbar content.
iOS 26+。代表系统组件的工具栏项。用于在工具栏布局中重新定位系统提供的项(如搜索)。
swift
struct DefaultToolbarItem {
    init(kind: ToolbarDefaultItemKind, placement: ToolbarItemPlacement = .automatic)
}
核心语义:如果系统已在工具栏中放置了匹配
kind
的项,
DefaultToolbarItem
会隐式替换默认放置的实例。这让你可以将系统项移动到不同位置,或相对于自己的工具栏内容重新定位它们。
Repositioning search between toolbar items
在工具栏项之间重新定位搜索
swift
NavigationSplitView {
    AllCalendarsView()
} detail: {
    SelectedCalendarView()
        .searchable(text: $query)
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                CalendarPicker()
            }
            ToolbarItem(placement: .bottomBar) {
                Invites()
            }
            DefaultToolbarItem(kind: .search, placement: .bottomBar)
            ToolbarSpacer(placement: .bottomBar)
            ToolbarItem(placement: .bottomBar) { NewEventButton() }
        }
}
swift
NavigationSplitView {
    AllCalendarsView()
} detail: {
    SelectedCalendarView()
        .searchable(text: $query)
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                CalendarPicker()
            }
            ToolbarItem(placement: .bottomBar) {
                Invites()
            }
            DefaultToolbarItem(kind: .search, placement: .bottomBar)
            ToolbarSpacer(placement: .bottomBar)
            ToolbarItem(placement: .bottomBar) { NewEventButton() }
        }
}
Specifying search column in collapsed NavigationSplitView
在折叠的NavigationSplitView中指定搜索列
Place
DefaultToolbarItem
with
.search
kind in the column that should display search when the split view collapses to compact (iPhone):
swift
NavigationSplitView {
    SidebarView()
        .toolbar {
            DefaultToolbarItem(kind: .search, placement: .bottomBar)
        }
} content: {
    ContentView()
} detail: {
    DetailView()
}
.searchable(text: $text)
This only applies when
.searchable()
is placed on the
NavigationSplitView
itself (not a child view).
在拆分视图折叠为紧凑模式(iPhone)时应显示搜索的列中放置带有
.search
类型的
DefaultToolbarItem
swift
NavigationSplitView {
    SidebarView()
        .toolbar {
            DefaultToolbarItem(kind: .search, placement: .bottomBar)
        }
} content: {
    ContentView()
} detail: {
    DetailView()
}
.searchable(text: $text)
仅当
.searchable()
放置在
NavigationSplitView
本身上(而非子视图)时才适用。
Availability check for backward compatibility
向后兼容性的可用性检查
swift
.toolbar {
    if #available(iOS 26.0, *) {
        DefaultToolbarItem(kind: .search, placement: .bottomBar)
        ToolbarSpacer(.flexible, placement: .bottomBar)
    }
    ToolbarItem(placement: .bottomBar) {
        NewNoteButton()
    }
}
.searchable(text: $searchText)
swift
.toolbar {
    if #available(iOS 26.0, *) {
        DefaultToolbarItem(kind: .search, placement: .bottomBar)
        ToolbarSpacer(.flexible, placement: .bottomBar)
    }
    ToolbarItem(placement: .bottomBar) {
        NewNoteButton()
    }
}
.searchable(text: $searchText)

Basic Morphing Setup

基础变形设置

Each view declares its own toolbar. iOS 26 morphs between them during navigation:
swift
struct MailboxList: View {
    var body: some View {
        List(mailboxes) { mailbox in
            NavigationLink(mailbox.name, value: mailbox)
        }
        .toolbar {  // ← Attached to this view, not NavigationStack
            ToolbarItem(placement: .bottomBar) {
                Button("Filter", systemImage: "line.3.horizontal.decrease") { }
            }
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(placement: .bottomBar) {
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}

struct MessageList: View {
    let mailbox: Mailbox

    var body: some View {
        List(mailbox.messages) { message in
            MessageRow(message: message)
        }
        .toolbar {  // ← Different toolbar — iOS 26 morphs between them
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(placement: .bottomBar) {
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}
#1 gotcha: If you attach
.toolbar {}
to the NavigationStack itself, iOS 26 has nothing to morph between — the toolbar stays static across all pushes.
每个视图都声明自己的工具栏。iOS 26在导航期间会在它们之间进行变形:
swift
struct MailboxList: View {
    var body: some View {
        List(mailboxes) { mailbox in
            NavigationLink(mailbox.name, value: mailbox)
        }
        .toolbar {  // ← 附加到该视图,而非NavigationStack
            ToolbarItem(placement: .bottomBar) {
                Button("Filter", systemImage: "line.3.horizontal.decrease") { }
            }
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(placement: .bottomBar) {
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}

struct MessageList: View {
    let mailbox: Mailbox

    var body: some View {
        List(mailbox.messages) { message in
            MessageRow(message: message)
        }
        .toolbar {  // ← 不同的工具栏——iOS 26在它们之间变形
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(placement: .bottomBar) {
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}
常见误区#1:如果将
.toolbar {}
附加到NavigationStack本身,iOS 26没有可变形的内容——工具栏在所有推送操作中保持静态。

Stable Items with
toolbar(id:)
and
ToolbarItem(id:)

使用
toolbar(id:)
ToolbarItem(id:)
实现稳定项

Items with matching IDs across screens stay in place during morphing (no bounce). Unmatched items animate in/out:
swift
struct MailboxList: View {
    var body: some View {
        List(mailboxes) { mailbox in
            NavigationLink(mailbox.name, value: mailbox)
        }
        .toolbar(id: "main") {
            ToolbarItem(id: "filter", placement: .bottomBar) {
                Button("Filter", systemImage: "line.3.horizontal.decrease") { }
            }
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(id: "compose", placement: .bottomBar) {
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}

struct MessageList: View {
    let mailbox: Mailbox

    var body: some View {
        List(mailbox.messages) { message in
            MessageRow(message: message)
        }
        .toolbar(id: "main") {
            // "filter" absent — animates out during push
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(id: "compose", placement: .bottomBar) {
                // Same ID as MailboxList — stays stable during morph
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}
不同屏幕上具有匹配ID的项在变形期间保持原位(无弹跳)。不匹配的项会动态进出:
swift
struct MailboxList: View {
    var body: some View {
        List(mailboxes) { mailbox in
            NavigationLink(mailbox.name, value: mailbox)
        }
        .toolbar(id: "main") {
            ToolbarItem(id: "filter", placement: .bottomBar) {
                Button("Filter", systemImage: "line.3.horizontal.decrease") { }
            }
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(id: "compose", placement: .bottomBar) {
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}

struct MessageList: View {
    let mailbox: Mailbox

    var body: some View {
        List(mailbox.messages) { message in
            MessageRow(message: message)
        }
        .toolbar(id: "main") {
            // "filter"不存在——推送时会动态消失
            ToolbarSpacer(.flexible, placement: .bottomBar)
            ToolbarItem(id: "compose", placement: .bottomBar) {
                // 与MailboxList中的ID相同——变形期间保持稳定
                Button("New Message", systemImage: "square.and.pencil") { }
            }
        }
    }
}

Complete Mail-Style Example

完整邮件风格示例

Combines DefaultToolbarItem, ToolbarSpacer, and stable compose button:
swift
struct MailApp: View {
    var body: some View {
        NavigationStack {
            MailboxList()
                .navigationDestination(for: Mailbox.self) { mailbox in
                    MessageList(mailbox: mailbox)
                }
                .navigationDestination(for: Message.self) { message in
                    MessageDetail(message: message)
                }
        }
    }
}

// Each destination defines its own toolbar — NavigationStack morphs between them
结合DefaultToolbarItem、ToolbarSpacer和稳定的撰写按钮:
swift
struct MailApp: View {
    var body: some View {
        NavigationStack {
            MailboxList()
                .navigationDestination(for: Mailbox.self) { mailbox in
                    MessageList(mailbox: mailbox)
                }
                .navigationDestination(for: Message.self) { message in
                    MessageDetail(message: message)
                }
        }
    }
}

// 每个目标视图都定义自己的工具栏——NavigationStack在它们之间变形

ToolbarSpacer(.flexible)

ToolbarSpacer(.flexible)

Pushes toolbar items apart (like Spacer in HStack). Complements
.fixed
for visual separation:
swift
.toolbar {
    ToolbarItem(placement: .bottomBar) {
        Button("Archive", systemImage: "archivebox") { }
    }
    ToolbarSpacer(.flexible, placement: .bottomBar)  // Push apart
    ToolbarItem(placement: .bottomBar) {
        Button("Compose", systemImage: "square.and.pencil") { }
    }
}
将工具栏项推开(类似于HStack中的Spacer)。与
.fixed
配合实现视觉分隔:
swift
.toolbar {
    ToolbarItem(placement: .bottomBar) {
        Button("Archive", systemImage: "archivebox") { }
    }
    ToolbarSpacer(.flexible, placement: .bottomBar)  // 推开项
    ToolbarItem(placement: .bottomBar) {
        Button("Compose", systemImage: "square.and.pencil") { }
    }
}

.navigationSubtitle()

.navigationSubtitle()

Add a secondary line below the navigation title:
swift
.navigationTitle("Inbox")
.navigationSubtitle("3 unread messages")
在导航标题下方添加次要行:
swift
.navigationTitle("Inbox")
.navigationSubtitle("3 unread messages")

User-Customizable Toolbars

用户可自定义工具栏

toolbar(id:content:)
also enables user customization — letting people rearrange, show, and hide toolbar items. This is the original purpose of the identified toolbar API (iOS 14+, iPadOS 16+ for customization).
toolbar(id:content:)
还支持用户自定义——允许用户重新排列、显示和隐藏工具栏项。这是带ID工具栏API的原始用途(iOS 14+, iPadOS 16+支持自定义)。

Setup

设置

swift
TextEditor(text: $text)
    .toolbar(id: "editingtools") {
        ToolbarItem(id: "bold", placement: .secondaryAction) {
            Toggle(isOn: $bold) { Image(systemName: "bold") }
        }
        ToolbarItem(id: "italic", placement: .secondaryAction) {
            Toggle(isOn: $italic) { Image(systemName: "italic") }
        }
    }
Platform constraint: Only
.secondaryAction
items support customization on iPadOS. Other placements follow normal rules and cannot be customized by the user.
swift
TextEditor(text: $text)
    .toolbar(id: "editingtools") {
        ToolbarItem(id: "bold", placement: .secondaryAction) {
            Toggle(isOn: $bold) { Image(systemName: "bold") }
        }
        ToolbarItem(id: "italic", placement: .secondaryAction) {
            Toggle(isOn: $italic) { Image(systemName: "italic") }
        }
    }
平台限制:在iPadOS上,只有
.secondaryAction
项支持自定义。其他位置遵循常规规则,用户无法自定义。

Controlling visibility

控制可见性

swift
ToolbarItem(id: "advanced", placement: .secondaryAction, showsByDefault: false) {
    // Hidden by default — user can add from customization editor
    AdvancedFormattingControls()
}
swift
ToolbarItem(id: "advanced", placement: .secondaryAction, showsByDefault: false) {
    // 默认隐藏——用户可从自定义编辑器中添加
    AdvancedFormattingControls()
}

macOS toolbar customization menu

macOS工具栏自定义菜单

Add
ToolbarCommands()
to enable the Customize Toolbar menu item:
swift
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            ToolbarCommands()
        }
    }
}
Users can also Control-click the toolbar to access the customization editor.
添加
ToolbarCommands()
以启用“自定义工具栏”菜单项:
swift
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            ToolbarCommands()
        }
    }
}
用户还可以按住Control键点击工具栏以访问自定义编辑器。

Related types

相关类型

  • CustomizableToolbarContent
    — Protocol for content that supports customization
  • ToolbarCustomizationBehavior
    — Control whether items can be added/removed
  • ToolbarCustomizationOptions
    — Options for the customization experience
  • CustomizableToolbarContent
    ——支持自定义的内容协议
  • ToolbarCustomizationBehavior
    ——控制项是否可添加/移除
  • ToolbarCustomizationOptions
    ——自定义体验的选项

ToolbarSpacer in customizable toolbars

可自定义工具栏中的ToolbarSpacer

Spacers are customizable items too — users can add, remove, and rearrange them from the customization panel. If a customizable toolbar supports a spacer of a given type, users can also add multiple copies:
swift
ContentView()
    .toolbar(id: "main-toolbar") {
        ToolbarItem(id: "tag") { TagButton() }
        ToolbarItem(id: "share") { ShareButton() }
        ToolbarSpacer(.fixed)
        ToolbarItem(id: "more") { MoreButton() }
    }
间隔器也是可自定义项——用户可从自定义面板添加、移除和重新排列它们。如果可自定义工具栏支持特定类型的间隔器,用户还可以添加多个副本:
swift
ContentView()
    .toolbar(id: "main-toolbar") {
        ToolbarItem(id: "tag") { TagButton() }
        ToolbarItem(id: "share") { ShareButton() }
        ToolbarSpacer(.fixed)
        ToolbarItem(id: "more") { MoreButton() }
    }

Scroll Edge Effects

滚动边缘效果

Automatic blur on scroll edges

滚动边缘自动模糊

swift
ScrollView {
    // When content scrolls under toolbar/navigation bar,
    // blur effect automatically ensures bar content remains legible
    ForEach(trips) { trip in
        TripRow(trip: trip)
    }
}
// No code required - automatic scroll edge blur
swift
ScrollView {
    // 当内容在工具栏/导航栏下滚动时,
    // 模糊效果自动确保栏内容保持可读
    ForEach(trips) { trip in
        TripRow(trip: trip)
    }
}
// 无需代码——自动应用滚动边缘模糊

Bottom-Aligned Search

底部对齐搜索

Foundational search APIs For
.searchable
,
isSearching
, suggestions, scopes, tokens, and programmatic search control, see
axiom-swiftui-search-ref
. This section covers iOS 26 refinements only.
基础搜索API 如需了解
.searchable
isSearching
、建议、范围、标记和程序化搜索控制,请参阅
axiom-swiftui-search-ref
。本节仅涵盖iOS 26的改进。

iPhone ergonomics

iPhone人体工程学

swift
NavigationSplitView {
    List { }
        .searchable(text: $searchText)
}
// Placement on NavigationSplitView automatically:
// - Bottom-aligned on iPhone (more ergonomic)
// - Top trailing corner on iPad
To restore pre-iOS 26 sidebar-embedded search on iPad, specify
placement: .sidebar
:
swift
NavigationSplitView {
    List { }
        .searchable(text: $searchText, placement: .sidebar)
        // Search field embedded in sidebar instead of floating glass container
}
swift
NavigationSplitView {
    List { }
        .searchable(text: $searchText)
}
// 放置在NavigationSplitView上会自动:
// - 在iPhone上底部对齐(更符合人体工程学)
// - 在iPad上位于右上角
要恢复iPad上iOS 26之前的侧边栏嵌入搜索,请指定
placement: .sidebar
swift
NavigationSplitView {
    List { }
        .searchable(text: $searchText, placement: .sidebar)
        // 搜索字段嵌入侧边栏,而非浮动玻璃容器
}

System Auto-Minimization

系统自动最小化

Depending on device size, number of toolbar buttons, and other factors, the system may choose to minimize the search field into a toolbar button automatically. When tapped, a full-width search field appears above the keyboard. Use
searchToolbarBehavior(.minimize)
to explicitly opt in to this behavior when search isn't the main part of your experience.
根据设备尺寸、工具栏按钮数量和其他因素,系统可能会自动选择将搜索字段最小化为工具栏按钮。点击时,会在键盘上方显示全宽搜索字段。当搜索不是体验的主要部分时,使用
searchToolbarBehavior(.minimize)
显式启用此行为。

Search Tab Role

搜索标签页角色

Searching in multi-tab apps is often done as a dedicated page. Set a search role on one of your tabs and place
.searchable
on the TabView:
swift
TabView {
    BrowseView()
        .tabItem { Label("Browse", systemImage: "square.grid.2x2") }

    Tab(role: .search) {
        SearchView()
    }
}
.searchable(text: $searchText)
When someone selects the search tab, the search field takes the place of the tab bar. People can interact with browsing suggestions or tap the search field to bring up the keyboard.
On iPad and Mac, when someone selects the search tab, the search field appears centered above the app's browsing suggestions — a different layout than iPhone's tab-bar replacement.
See swiftui-nav-ref skill Section 5.5 (iOS 26 Tab Features) for full
Tab(role: .search)
patterns.
在多标签页应用中,搜索通常作为专用页面完成。在其中一个标签页上设置搜索角色,并将
.searchable
放置在TabView上:
swift
TabView {
    BrowseView()
        .tabItem { Label("Browse", systemImage: "square.grid.2x2") }

    Tab(role: .search) {
        SearchView()
    }
}
.searchable(text: $searchText)
当用户选择搜索标签页时,搜索字段会取代标签栏。用户可以与浏览建议交互,或点击搜索字段调出键盘。
在iPad和Mac上,当用户选择搜索标签页时,搜索字段会居中显示在应用浏览建议上方——这与iPhone的标签栏替换布局不同。
有关完整的
Tab(role: .search)
模式,请参阅swiftui-nav-ref技能第5.5节(iOS 26标签页特性)。

Glass Effect for Custom Views

自定义视图的玻璃效果

swift
struct ToTopButton: View {
    var body: some View {
        Button("To Top", systemImage: "chevron.up") {
            scrollToTop()
        }
        .padding()
        .glassEffect() // Reflects surrounding content
    }

    func scrollToTop() { }
}
SwiftUI automatically uses a vibrant text color that adapts to maintain legibility against colorful backgrounds. Tints also use vibrant color that adapts to the content behind them.
On iOS, add the
.interactive
modifier to the glass effect for custom controls or containers with interactive elements. Glass reacts to user interaction by scaling, bouncing, and shimmering.
swift
struct ToTopButton: View {
    var body: some View {
        Button("To Top", systemImage: "chevron.up") {
            scrollToTop()
        }
        .padding()
        .glassEffect() // 反射周围内容
    }

    func scrollToTop() { }
}
SwiftUI自动使用鲜艳的文本颜色,可适应多彩背景以保持可读性。着色也会使用可适应背后内容的鲜艳颜色。
在iOS上,为自定义控件或带有交互元素的容器的玻璃效果添加
.interactive
修饰符。玻璃会通过缩放、弹跳和闪烁响应用户交互。

GlassEffectContainer

GlassEffectContainer

Combine multiple glass elements with
GlassEffectContainer
. This is essential for visual correctness — glass samples content from an area larger than itself, but glass cannot sample other glass. Nearby glass elements in different containers produce inconsistent behavior:
swift
GlassEffectContainer {
    HStack(spacing: 12) {
        ForEach(badges) { badge in
            BadgeView(badge: badge)
                .glassEffect()
        }
    }
}
使用
GlassEffectContainer
组合多个玻璃元素。这对视觉正确性至关重要——玻璃会从比自身更大的区域采样内容,但玻璃无法采样其他玻璃。不同容器中的邻近玻璃元素会产生不一致的行为:
swift
GlassEffectContainer {
    HStack(spacing: 12) {
        ForEach(badges) { badge in
            BadgeView(badge: badge)
                .glassEffect()
        }
    }
}

glassEffectID for Morphing Transitions

用于变形过渡的glassEffectID

Use
glassEffectID
with a namespace to create fluid morphing transitions between glass elements:
swift
@Namespace private var badgeNamespace

var body: some View {
    if isExpanded {
        // Expanded badge stack
        GlassEffectContainer {
            VStack {
                ForEach(badges) { badge in
                    BadgeView(badge: badge)
                        .glassEffect()
                        .glassEffectID(badge.id, in: badgeNamespace)
                }
            }
        }
    } else {
        // Collapsed into toolbar button
        Button("Badges", systemImage: "star.fill") {
            isExpanded.toggle()
        }
        .glassEffect()
        .glassEffectID("collapsed", in: badgeNamespace)
    }
}
// Tapping the button expands badges with fluid glass morphing;
// tapping again re-absorbs them into the button
使用
glassEffectID
和命名空间在玻璃元素之间创建流畅的变形过渡:
swift
@Namespace private var badgeNamespace

var body: some View {
    if isExpanded {
        // 展开的徽章堆叠
        GlassEffectContainer {
            VStack {
                ForEach(badges) { badge in
                    BadgeView(badge: badge)
                        .glassEffect()
                        .glassEffectID(badge.id, in: badgeNamespace)
                }
            }
        }
    } else {
        // 折叠为工具栏按钮
        Button("Badges", systemImage: "star.fill") {
            isExpanded.toggle()
        }
        .glassEffect()
        .glassEffectID("collapsed", in: badgeNamespace)
    }
}
// 点击按钮会使徽章以流畅的玻璃变形效果展开;再次点击会将它们重新吸收到按钮中

Sheets with Liquid Glass

带Liquid Glass的Sheet

Remove presentationBackground

移除presentationBackground

Partial height sheets are inset by default with a Liquid Glass background. At smaller heights, bottom edges pull in, nesting in the curved edges of the display. When transitioning to full height, the glass gradually becomes opaque and anchors to the screen edge.
If you've used
presentationBackground
to apply a custom background to sheets, consider removing it and let the new material shine:
swift
// ❌ Custom background interferes with Liquid Glass sheet material
.sheet(isPresented: $showDetail) {
    DetailView()
        .presentationBackground(.thinMaterial)  // Remove this
}

// ✅ System applies Liquid Glass automatically
.sheet(isPresented: $showDetail) {
    DetailView()
}
部分高度的Sheet默认使用Liquid Glass背景并向内缩进。在较小高度时,底部边缘向内收缩,嵌套在显示屏的弧形边缘中。过渡到全高度时,玻璃会逐渐变为不透明并锚定到屏幕边缘。
如果你曾使用
presentationBackground
为Sheet应用自定义背景,请考虑移除它,让新材质发挥作用:
swift
// ❌ 自定义背景会干扰Liquid Glass Sheet材质
.sheet(isPresented: $showDetail) {
    DetailView()
        .presentationBackground(.thinMaterial)  // 移除此行
}

// ✅ 系统自动应用Liquid Glass
.sheet(isPresented: $showDetail) {
    DetailView()
}

Sheet Morphing from Buttons

从按钮变形的Sheet

Sheets can morph directly out of the buttons that present them. Use
navigationZoomTransition
to connect the source view to the sheet content:
swift
.toolbar {
    ToolbarItem(placement: .topBarTrailing) {
        Button("Details", systemImage: "info.circle") {
            showDetails = true
        }
        .matchedTransitionSource(id: "details", in: namespace)
    }
}
.sheet(isPresented: $showDetails) {
    DetailsSheet()
        .navigationTransition(.zoom(sourceID: "details", in: namespace))
}
Menus, alerts, and popovers also flow smoothly out of Liquid Glass controls. Dialogs automatically morph out of the buttons that present them — no additional code needed.
Sheet可以直接从呈现它们的按钮变形出来。使用
navigationZoomTransition
将源视图连接到Sheet内容:
swift
.toolbar {
    ToolbarItem(placement: .topBarTrailing) {
        Button("Details", systemImage: "info.circle") {
            showDetails = true
        }
        .matchedTransitionSource(id: "details", in: namespace)
    }
}
.sheet(isPresented: $showDetails) {
    DetailsSheet()
        .navigationTransition(.zoom(sourceID: "details", in: namespace))
}
菜单、警报和弹出框也会从Liquid Glass控件流畅地弹出。对话框会自动从呈现它们的按钮变形出来——无需额外代码。

System Controls Updates

系统控件更新

Controls now have the new design automatically:
  • Toggles
  • Segmented pickers
  • Sliders
Reference "Build a SwiftUI app with the new design" (WWDC 2025-323) for adoption best practices and advanced customizations.
控件现在会自动获得新设计:
  • 开关(Toggles)
  • 分段选择器(Segmented pickers)
  • 滑块(Sliders)
参考 “使用新设计构建SwiftUI应用”(WWDC 2025-323)了解采用最佳实践和高级自定义。

Button Shape Changes

按钮形状变化

Bordered buttons now have a capsule shape by default, harmonious with the curved corners of the new design. On macOS, mini, small, and medium size controls retain a rounded-rectangle shape to preserve horizontal density.
swift
// Capsule is now the default bordered shape on iOS
Button("Action") { }
    .buttonStyle(.bordered)  // Capsule shape on iOS 26

// Override with buttonBorderShape if needed
Button("Action") { }
    .buttonStyle(.bordered)
    .buttonBorderShape(.roundedRectangle)  // Force rounded rectangle
带边框的按钮现在默认采用胶囊形状,与新设计的圆角和谐统一。在macOS上,迷你、小和中等尺寸的控件保留圆角矩形形状以保持水平密度。
swift
// 在iOS上,胶囊现在是默认的带边框形状
Button("Action") { }
    .buttonStyle(.bordered)  // iOS 26上为胶囊形状

// 如有需要,使用buttonBorderShape覆盖
Button("Action") { }
    .buttonStyle(.bordered)
    .buttonBorderShape(.roundedRectangle)  // 强制使用圆角矩形

Extra Large Buttons

超大按钮

For prominent actions, iOS 26 adds support for extra large sized buttons:
swift
Button("Get Started") { startOnboarding() }
    .buttonStyle(.borderedProminent)
    .controlSize(.extraLarge)  // New extra-large size
对于突出操作,iOS 26添加了对超大尺寸按钮的支持:
swift
Button("Get Started") { startOnboarding() }
    .buttonStyle(.borderedProminent)
    .controlSize(.extraLarge)  // 新的超大尺寸

Updated Control Heights

更新的控件高度

Control heights are updated for the new design. Most controls on macOS are slightly taller, providing more breathing room around labels and enhancing click targets.
For compatibility with existing high-density layouts (complex inspectors, popovers), apply
controlSize
to a single control or an entire container:
swift
// Preserve density in complex inspectors
Form {
    // controls here
}
.controlSize(.small)  // Maintains pre-iOS 26 density
为新设计更新了控件高度。macOS上的大多数控件略高,为标签提供了更多呼吸空间并增强了点击目标。
为了与现有的高密度布局(复杂检查器、弹出框)兼容,将
controlSize
应用于单个控件或整个容器:
swift
// 在复杂检查器中保留密度
Form {
    // 控件位于此处
}
.controlSize(.small)  // 保持iOS 26之前的密度

Menu Cross-Platform Consistency

跨平台菜单一致性

Menus across platforms have a new design and more consistent layout. Icons are consistently on the leading edge and are now used on macOS too. The same API using
Label
or standard control initializers now creates the same result on both platforms:
swift
Menu("Actions") {
    Button("Copy", systemImage: "doc.on.doc") { copy() }
    Button("Paste", systemImage: "doc.on.clipboard") { paste() }
    Button("Delete", systemImage: "trash", role: .destructive) { delete() }
}
// Same result on iOS and macOS — icons on leading edge
各平台的菜单都有了新设计和更一致的布局。图标始终位于左侧,现在macOS也使用图标。使用
Label
或标准控件初始化器的相同API现在在两个平台上产生相同的结果:
swift
Menu("Actions") {
    Button("Copy", systemImage: "doc.on.doc") { copy() }
    Button("Paste", systemImage: "doc.on.clipboard") { paste() }
    Button("Delete", systemImage: "trash", role: .destructive) { delete() }
}
// iOS和macOS上的结果相同——图标位于左侧

Concentric Rectangle Shape

同心矩形形状

Controls align their corners perfectly within their containers — even the device itself. This is corner concentricity. A button at the bottom of a sheet should share the same corner center with the sheet.
swift
// Automatic concentricity with container
Button("Confirm") { confirm() }
    .clipShape(.rect(cornerRadius: 12, style: .containerConcentric))
    // Shape automatically matches container across displays and window shapes
Use case: Buttons positioned at the edges of sheets, popovers, or cards that need their corners to feel visually nested within the container.

控件的角落与其容器(甚至设备本身)完美对齐。这就是角落同心度。Sheet底部的按钮应与Sheet共享相同的角中心。
swift
// 与容器自动同心
Button("Confirm") { confirm() }
    .clipShape(.rect(cornerRadius: 12, style: .containerConcentric))
    // 形状会自动匹配所有显示器和窗口形状的容器
用例:位于Sheet、弹出框或卡片边缘的按钮,其角落需要在视觉上嵌套在容器中。

Slider Enhancements

滑块增强

iOS 26 introduces major enhancements to
Slider
: custom tick marks, constrained selection ranges, current value labels, and thumb visibility control.
iOS 26为
Slider
引入了重大增强:自定义刻度标记、约束选择范围、当前值标签和滑块thumb可见性控制。

Slider Ticks API

滑块刻度API

Core Types

核心类型

TypePurpose
SliderTick<V>
Individual tick at a specific value with optional label
SliderTickContentForEach
Iterate over collection to create multiple ticks
SliderTickBuilder
Result builder for composing tick content
TupleSliderTickContent
Internal type for multiple inline ticks
SliderTickContent
Protocol that all tick types conform to
类型用途
SliderTick<V>
特定值的单个刻度,可选标签
SliderTickContentForEach
遍历集合以创建多个刻度
SliderTickBuilder
用于组合刻度内容的结果构建器
TupleSliderTickContent
多个内联刻度的内部类型
SliderTickContent
所有刻度类型都遵循的协议

Basic Ticks

基础刻度

Static tick marks

静态刻度标记

swift
struct SpeedSlider: View {
    @State private var speed: Double = 0.5

    var body: some View {
        Slider(value: $speed) {
            Text("Speed")
        } ticks: {
            SliderTick(0.2)
            SliderTick(0.5)
            SliderTick(0.8)
        }
    }
}
swift
struct SpeedSlider: View {
    @State private var speed: Double = 0.5

    var body: some View {
        Slider(value: $speed) {
            Text("Speed")
        } ticks: {
            SliderTick(0.2)
            SliderTick(0.5)
            SliderTick(0.8)
        }
    }
}

Labeled Ticks

带标签的刻度

Ticks with custom labels

带自定义标签的刻度

swift
Slider(value: $value, in: 0...10) {
    Text("Rating")
} ticks: {
    SliderTick(0) { Text("Min") }
    SliderTick(5) { Text("Mid") }
    SliderTick(10) { Text("Max") }
}
swift
Slider(value: $value, in: 0...10) {
    Text("Rating")
} ticks: {
    SliderTick(0) { Text("Min") }
    SliderTick(5) { Text("Mid") }
    SliderTick(10) { Text("Max") }
}

Dynamic Ticks with SliderTickContentForEach

使用SliderTickContentForEach的动态刻度

Iterate over values to create ticks

遍历值以创建刻度

swift
struct TemperatureSlider: View {
    @State private var temp: Float = 70

    var body: some View {
        let stops: [Float] = stride(from: 60, through: 80, by: 5).map { Float($0) }

        Slider(value: $temp, in: 60...80) {
            Text("Temperature")
        } ticks: {
            SliderTickContentForEach(stops, id: \.self) { value in
                SliderTick(value) {
                    Text("\(Int(value))°")
                        .font(.caption2)
                }
            }
        }
    }
}
swift
struct TemperatureSlider: View {
    @State private var temp: Float = 70

    var body: some View {
        let stops: [Float] = stride(from: 60, through: 80, by: 5).map { Float($0) }

        Slider(value: $temp, in: 60...80) {
            Text("Temperature")
        } ticks: {
            SliderTickContentForEach(stops, id: \.self) { value in
                SliderTick(value) {
                    Text("\(Int(value))°")
                        .font(.caption2)
                }
            }
        }
    }
}

Custom Data with Ticks (API Constraint)

带刻度的自定义数据(API约束)

Important
SliderTickContentForEach
requires
Data.Element
to match the
SliderTick<V>
value type. You cannot iterate directly over custom structs.
重要
SliderTickContentForEach
要求
Data.Element
SliderTick<V>
值类型匹配。你不能直接遍历自定义结构体。

❌ This won't compile

❌ 无法编译

swift
struct Chapter {
    let time: Double
    let name: String
    let id: UUID
}

// ERROR: Data.Element (Chapter) doesn't match SliderTick value type (Double)
SliderTickContentForEach(chapters, id: \.id) { chapter in
    SliderTick(chapter.time) { Text(chapter.name) }
}
swift
struct Chapter {
    let time: Double
    let name: String
    let id: UUID
}

// 错误:Data.Element (Chapter)与SliderTick值类型(Double)不匹配
SliderTickContentForEach(chapters, id: \.id) { chapter in
    SliderTick(chapter.time) { Text(chapter.name) }
}

✅ Workaround: Extract values, look up labels

✅ 解决方法:提取值,查找标签

swift
struct ChapterSlider: View {
    @Binding var currentTime: Double
    let chapters: [Chapter]
    let duration: Double

    var body: some View {
        Slider(value: $currentTime, in: 0...duration) {
            Text("Time")
        } ticks: {
            // Iterate over Double values, not Chapter structs
            SliderTickContentForEach(chapters.map(\.time), id: \.self) { time in
                SliderTick(time) {
                    // Look up chapter name for label
                    if let chapter = chapters.first(where: { $0.time == time }) {
                        Text(chapter.name)
                            .font(.caption2)
                    }
                }
            }
        }
    }
}
Why The API ties tick positions to slider values (
BinaryFloatingPoint
). The type system enforces this so ticks align correctly with the slider's value range.
swift
struct ChapterSlider: View {
    @Binding var currentTime: Double
    let chapters: [Chapter]
    let duration: Double

    var body: some View {
        Slider(value: $currentTime, in: 0...duration) {
            Text("Time")
        } ticks: {
            // 遍历Double值,而非Chapter结构体
            SliderTickContentForEach(chapters.map(\.time), id: \.self) { time in
                SliderTick(time) {
                    // 查找标签对应的章节名称
                    if let chapter = chapters.first(where: { $0.time == time }) {
                        Text(chapter.name)
                            .font(.caption2)
                    }
                }
            }
        }
    }
}
原因 API将刻度位置与滑块值(
BinaryFloatingPoint
)绑定。类型系统强制执行此操作,以便刻度与滑块的值范围正确对齐。

Advanced Slider Initializers

高级滑块初始化器

Full-featured slider with ticks

带刻度的全功能滑块

swift
Slider(
    value: $rating,
    in: 0...100,
    neutralValue: 50,           // Starting point / center value
    enabledBounds: 20...80,     // Restrict selectable range
    label: { Text("Rating") },
    currentValueLabel: { Text("\(Int(rating))") },
    minimumValueLabel: { Text("0") },
    maximumValueLabel: { Text("100") },
    ticks: {
        SliderTick(20) { Text("Min") }
        SliderTick(50) { Text("Neutral") }
        SliderTick(80) { Text("Max") }
    },
    onEditingChanged: { editing in
        print(editing ? "Started" : "Ended")
    }
)
swift
Slider(
    value: $rating,
    in: 0...100,
    neutralValue: 50,           // 起始点/中心值
    enabledBounds: 20...80,     // 限制可选范围
    label: { Text("Rating") },
    currentValueLabel: { Text("\(Int(rating))") },
    minimumValueLabel: { Text("0") },
    maximumValueLabel: { Text("100") },
    ticks: {
        SliderTick(20) { Text("Min") }
        SliderTick(50) { Text("Neutral") }
        SliderTick(80) { Text("Max") }
    },
    onEditingChanged: { editing in
        print(editing ? "Started" : "Ended")
    }
)

Parameters

参数

ParameterTypePurpose
value
Binding<V>
Current slider value
bounds
ClosedRange<V>
Full value range (default:
0...1
)
step
V.Stride
Increment between valid values
neutralValue
V?
Starting/center point
enabledBounds
ClosedRange<V>?
Restrict which values are selectable
ticks
@SliderTickBuilder
Custom tick marks
currentValueLabel
@ViewBuilder
Shows current value
onEditingChanged
(Bool) -> Void
Called when editing starts/ends
参数类型用途
value
Binding<V>
当前滑块值
bounds
ClosedRange<V>
完整值范围(默认:
0...1
step
V.Stride
有效值之间的增量
neutralValue
V?
起始/中心点
enabledBounds
ClosedRange<V>?
限制可选值范围
ticks
@SliderTickBuilder
自定义刻度标记
currentValueLabel
@ViewBuilder
显示当前值
onEditingChanged
(Bool) -> Void
编辑开始/结束时调用

Step-Based Ticks

基于步长的刻度

Automatic ticks at each step

每个步长自动生成刻度

swift
Slider(
    value: $volume,
    in: 0...10,
    step: 2,
    label: { Text("Volume") },
    tick: { value in
        // Called for each step value (0, 2, 4, 6, 8, 10)
        SliderTick(value) {
            Text("\(Int(value))")
        }
    }
)
swift
Slider(
    value: $volume,
    in: 0...10,
    step: 2,
    label: { Text("Volume") },
    tick: { value in
        // 为每个步长值调用(0, 2, 4, 6, 8, 10)
        SliderTick(value) {
            Text("\(Int(value))")
        }
    }
)

sliderThumbVisibility

sliderThumbVisibility

Hide slider thumb for minimal interfaces

在极简界面中隐藏滑块thumb

swift
struct MediaControlView: View {
    @State private var progress: CGFloat = 0.5

    var body: some View {
        Slider(value: $progress)
            .sliderThumbVisibility(.hidden)
            .padding(.horizontal, 16)
    }
}
Visibility options
  • .automatic
    — System default (usually visible)
  • .visible
    — Always show thumb
  • .hidden
    — Hide thumb
Use cases
  • Media player progress indicators
  • Read-only value displays
  • Minimal UI designs where slider acts as progress view
  • Interactive sliders where visual focus should be on track, not thumb
Note On watchOS, the slider thumb is always visible regardless of this setting.
swift
struct MediaControlView: View {
    @State private var progress: CGFloat = 0.5

    var body: some View {
        Slider(value: $progress)
            .sliderThumbVisibility(.hidden)
            .padding(.horizontal, 16)
    }
}
可见性选项
  • .automatic
    ——系统默认(通常可见)
  • .visible
    ——始终显示thumb
  • .hidden
    ——隐藏thumb
用例
  • 媒体播放器进度指示器
  • 只读值显示
  • 极简UI设计,其中滑块用作进度视图
  • 交互式滑块,视觉焦点应在轨道而非thumb上
注意 在watchOS上,无论此设置如何,滑块thumb始终可见。

Complete Media Player Example

完整媒体播放器示例

swift
struct MediaPlayerControls: View {
    @State private var currentTime: Double = 0
    let duration: Double = 300 // 5 minutes
    let chapters: [Chapter] = [
        Chapter(time: 0, name: "Intro", id: UUID()),
        Chapter(time: 60, name: "Verse 1", id: UUID()),
        Chapter(time: 120, name: "Chorus", id: UUID()),
        Chapter(time: 180, name: "Verse 2", id: UUID()),
        Chapter(time: 240, name: "Outro", id: UUID())
    ]

    var body: some View {
        VStack {
            // Time display
            HStack {
                Text(formatTime(currentTime))
                Spacer()
                Text(formatTime(duration))
            }
            .font(.caption)

            // Slider with chapter ticks
            Slider(
                value: $currentTime,
                in: 0...duration,
                label: { Text("Playback") },
                currentValueLabel: {
                    if let chapter = currentChapter {
                        Text(chapter.name)
                            .font(.caption)
                    }
                },
                ticks: {
                    SliderTickContentForEach(chapters.map(\.time), id: \.self) { time in
                        SliderTick(time)
                    }
                }
            )
            .sliderThumbVisibility(.hidden)
        }
        .padding()
    }

    var currentChapter: Chapter? {
        chapters.last { $0.time <= currentTime }
    }

    func formatTime(_ seconds: Double) -> String {
        let mins = Int(seconds) / 60
        let secs = Int(seconds) % 60
        return String(format: "%d:%02d", mins, secs)
    }
}

swift
struct MediaPlayerControls: View {
    @State private var currentTime: Double = 0
    let duration: Double = 300 // 5分钟
    let chapters: [Chapter] = [
        Chapter(time: 0, name: "Intro", id: UUID()),
        Chapter(time: 60, name: "Verse 1", id: UUID()),
        Chapter(time: 120, name: "Chorus", id: UUID()),
        Chapter(time: 180, name: "Verse 2", id: UUID()),
        Chapter(time: 240, name: "Outro", id: UUID())
    ]

    var body: some View {
        VStack {
            // 时间显示
            HStack {
                Text(formatTime(currentTime))
                Spacer()
                Text(formatTime(duration))
            }
            .font(.caption)

            // 带章节刻度的滑块
            Slider(
                value: $currentTime,
                in: 0...duration,
                label: { Text("Playback") },
                currentValueLabel: {
                    if let chapter = currentChapter {
                        Text(chapter.name)
                            .font(.caption)
                    }
                },
                ticks: {
                    SliderTickContentForEach(chapters.map(\.time), id: \.self) { time in
                        SliderTick(time)
                    }
                }
            )
            .sliderThumbVisibility(.hidden)
        }
        .padding()
    }

    var currentChapter: Chapter? {
        chapters.last { $0.time <= currentTime }
    }

    func formatTime(_ seconds: Double) -> String {
        let mins = Int(seconds) / 60
        let secs = Int(seconds) % 60
        return String(format: "%d:%02d", mins, secs)
    }
}

New View Modifiers

新视图修饰符

safeAreaBar

safeAreaBar

Sticky bars with progressive blur

带渐进模糊的粘性栏

swift
struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                ForEach(1...20, id: \.self) { index in
                    Text("\(index). Item")
                }
            }
            .safeAreaBar(edge: .bottom) {
                Text("Bottom Action Bar")
                    .padding(.vertical, 15)
            }
            .scrollEdgeEffectStyle(.soft, for: .bottom)
            // Alternative: .scrollEdgeEffectStyle(.hard, for: .bottom)
        }
    }
}
Features
  • Works like
    safeAreaInset
    but with integrated blur
  • Progressive blur (
    .soft
    ) or hard blur (
    .hard
    ) via
    scrollEdgeEffectStyle
  • Automatically respects safe areas
  • Bar remains fixed while content scrolls beneath
Use cases
  • Action bars that remain visible while scrolling
  • Fixed controls at screen edges
  • Bottom toolbars with scroll blur
swift
struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                ForEach(1...20, id: \.self) { index in
                    Text("\(index). Item")
                }
            }
            .safeAreaBar(edge: .bottom) {
                Text("Bottom Action Bar")
                    .padding(.vertical, 15)
            }
            .scrollEdgeEffectStyle(.soft, for: .bottom)
            // 替代方案:.scrollEdgeEffectStyle(.hard, for: .bottom)
        }
    }
}
特性
  • 类似于
    safeAreaInset
    ,但集成了模糊效果
  • 通过
    scrollEdgeEffectStyle
    提供渐进模糊(
    .soft
    )或硬模糊(
    .hard
  • 自动尊重安全区域
  • 栏保持固定,内容在其下方滚动
用例
  • 滚动时保持可见的操作栏
  • 屏幕边缘的固定控件
  • 带滚动模糊的底部工具栏

onOpenURL Enhancement

onOpenURL增强

Open links in in-app browser

在应用内浏览器中打开链接

swift
struct LinkView: View {
    @Environment(\.openURL) var openURL

    var body: some View {
        let website = URL(string: "https://example.com")!

        VStack {
            // Old style - opens in Safari
            Link(destination: website) {
                Text("Open in Safari")
            }

            // New style - opens in-app (iOS 26+)
            Button("Open In-App") {
                openURL(website, prefersInApp: true)
            }
            .buttonStyle(.borderedProminent)
        }
    }
}
Key difference
  • Default
    Link
    opens in Safari app
  • openURL(url, prefersInApp: true)
    opens in SFSafariViewController-style in-app browser
  • Keeps users in your app
  • Preserves navigation flow
swift
struct LinkView: View {
    @Environment(\.openURL) var openURL

    var body: some View {
        let website = URL(string: "https://example.com")!

        VStack {
            // 旧样式——在Safari中打开
            Link(destination: website) {
                Text("Open in Safari")
            }

            // 新样式——在应用内打开(iOS 26+)
            Button("Open In-App") {
                openURL(website, prefersInApp: true)
            }
            .buttonStyle(.borderedProminent)
        }
    }
}
核心区别
  • 默认
    Link
    在Safari应用中打开
  • openURL(url, prefersInApp: true)
    在SFSafariViewController样式的应用内浏览器中打开
  • 让用户留在你的应用中
  • 保留导航流程

Button Roles (.close and .confirm)

按钮角色(.close和.confirm)

System-styled close and confirm buttons

系统样式的关闭和确认按钮

swift
struct ModalView: View {
    @State private var showSheet = false

    var body: some View {
        Button("Show Sheet") {
            showSheet.toggle()
        }
        .sheet(isPresented: $showSheet) {
            NavigationStack {
                VStack {}
                    .navigationTitle("Info")
                    .toolbar {
                        ToolbarSpacer(.flexible, placement: .topBarTrailing)

                        ToolbarItem(placement: .topBarTrailing) {
                            Button(role: .close) {
                                showSheet = false
                            }
                        }
                    }
            }
            .presentationDetents([.medium])
        }
    }
}
Features
  • Button(role: .close)
    renders as X icon with glass effect in toolbars
  • Button(role: .confirm)
    provides system-styled confirmation button
  • No custom label needed
  • Consistent with system modals and sheets
Use cases
  • Modal dismissal
  • Sheet close buttons
  • Confirmation dialogs
  • Native-looking dismiss actions
swift
struct ModalView: View {
    @State private var showSheet = false

    var body: some View {
        Button("Show Sheet") {
            showSheet.toggle()
        }
        .sheet(isPresented: $showSheet) {
            NavigationStack {
                VStack {}
                    .navigationTitle("Info")
                    .toolbar {
                        ToolbarSpacer(.flexible, placement: .topBarTrailing)

                        ToolbarItem(placement: .topBarTrailing) {
                            Button(role: .close) {
                                showSheet = false
                            }
                        }
                    }
            }
            .presentationDetents([.medium])
        }
    }
}
特性
  • Button(role: .close)
    在工具栏中渲染为带玻璃效果的X图标
  • Button(role: .confirm)
    提供系统样式的确认按钮
  • 无需自定义标签
  • 与系统模态框和Sheet一致
用例
  • 模态框关闭
  • Sheet关闭按钮
  • 确认对话框
  • 原生风格的关闭操作

GlassButtonStyle (iOS 26.1+)

GlassButtonStyle(iOS 26.1+)

Glass button variations

玻璃按钮变体

swift
struct GlassButtonExample: View {
    var body: some View {
        ZStack {
            Image(.background)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .ignoresSafeArea()

            VStack(spacing: 16) {
                Button("Clear Glass") {}
                    .buttonStyle(GlassButtonStyle(.clear))

                Button("Regular Glass") {}
                    .buttonStyle(GlassButtonStyle(.glass))

                Button("Tinted Glass") {}
                    .buttonStyle(GlassButtonStyle(.tint))
                    .tint(.blue)
            }
            .fontWeight(.bold)
            .foregroundStyle(.white)
            .buttonSizing(.flexible)
            .font(.title)
            .padding()
        }
    }
}
Variants
  • .clear
    — Transparent glass effect
  • .glass
    — Standard glass appearance
  • .tint
    — Colored glass (use with
    .tint()
    modifier)
Requires iOS 26.1+ (not available in initial iOS 26.0 release)
swift
struct GlassButtonExample: View {
    var body: some View {
        ZStack {
            Image(.background)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .ignoresSafeArea()

            VStack(spacing: 16) {
                Button("Clear Glass") {}
                    .buttonStyle(GlassButtonStyle(.clear))

                Button("Regular Glass") {}
                    .buttonStyle(GlassButtonStyle(.glass))

                Button("Tinted Glass") {}
                    .buttonStyle(GlassButtonStyle(.tint))
                    .tint(.blue)
            }
            .fontWeight(.bold)
            .foregroundStyle(.white)
            .buttonSizing(.flexible)
            .font(.title)
            .padding()
        }
    }
}
变体
  • .clear
    ——透明玻璃效果
  • .glass
    ——标准玻璃外观
  • .tint
    ——彩色玻璃(与
    .tint()
    修饰符一起使用)
要求 iOS 26.1+(初始iOS 26.0版本中不可用)

buttonSizing

buttonSizing

Control button layout behavior

控制按钮布局行为

swift
struct ButtonLayoutExample: View {
    var body: some View {
        VStack(spacing: 16) {
            Button("Fit Content") {}
                .buttonSizing(.fit)
            // Button shrinks to label size

            Button("Stretch Full Width") {}
                .buttonSizing(.stretch)
            // Button expands to fill available space

            Button("Flexible") {}
                .buttonSizing(.flexible)
            // Balanced between fit and stretch
        }
        .padding()
    }
}
Options
  • .fit
    — Button fits label size
  • .stretch
    — Button fills available width
  • .flexible
    — Balanced sizing (context-dependent)
Works with
  • Plain text buttons
  • Custom labels (icon + text, HStack/VStack)
  • All button styles
swift
struct ButtonLayoutExample: View {
    var body: some View {
        VStack(spacing: 16) {
            Button("Fit Content") {}
                .buttonSizing(.fit)
            // 按钮缩小到标签大小

            Button("Stretch Full Width") {}
                .buttonSizing(.stretch)
            // 按钮扩展以填充可用空间

            Button("Flexible") {}
                .buttonSizing(.flexible)
            // 在适应和拉伸之间取得平衡
        }
        .padding()
    }
}
选项
  • .fit
    ——按钮适应标签大小
  • .stretch
    ——按钮填充可用宽度
  • .flexible
    ——平衡大小(取决于上下文)
适用对象
  • 纯文本按钮
  • 自定义标签(图标+文本、HStack/VStack)
  • 所有按钮样式

searchToolbarBehavior

searchToolbarBehavior

Foundational search APIs For the
.searchable
modifier, placement options, and search suggestions that
.searchToolbarBehavior
builds upon, see
axiom-swiftui-search-ref
.
基础搜索API 如需了解
.searchable
修饰符、位置选项以及
searchToolbarBehavior
所基于的搜索建议,请参阅
axiom-swiftui-search-ref

Compact search that expands on focus

获得焦点时展开的紧凑搜索

swift
struct SearchView: View {
    @State private var searchText = ""

    var body: some View {
        NavigationStack {
            List {
                Text("User 1")
                Text("User 2")
                Text("User 3")
            }
            .navigationTitle("Search Users")
            .searchable(text: $searchText)
            .searchToolbarBehavior(.minimize)
            .toolbar {
                ToolbarSpacer(.flexible, placement: .bottomBar)
                DefaultToolbarItem(kind: .search, placement: .bottomBar)
            }
        }
    }
}
SearchToolbarBehavior
options
:
  • .minimize
    — Search field compact (button-like) when unfocused, expands on tap
  • .automatic
    — System default behavior (full search field)
Behavior
  • Similar to Tab Bar search pattern
  • Saves toolbar space
  • Cleaner UI when search not in use
Use cases
  • List/content-heavy screens
  • Crowded navigation bars
  • Tab bar style search on regular screens
Backward-compatible wrapper for apps targeting iOS 18+26:
swift
extension View {
    @ViewBuilder func minimizedSearch() -> some View {
        if #available(iOS 26.0, *) {
            self.searchToolbarBehavior(.minimize)
        } else { self }
    }
}

// Usage
.searchable(text: $searchText)
.minimizedSearch()
swift
struct SearchView: View {
    @State private var searchText = ""

    var body: some View {
        NavigationStack {
            List {
                Text("User 1")
                Text("User 2")
                Text("User 3")
            }
            .navigationTitle("Search Users")
            .searchable(text: $searchText)
            .searchToolbarBehavior(.minimize)
            .toolbar {
                ToolbarSpacer(.flexible, placement: .bottomBar)
                DefaultToolbarItem(kind: .search, placement: .bottomBar)
            }
        }
    }
}
SearchToolbarBehavior
选项
:
  • .minimize
    ——未获得焦点时搜索字段紧凑(类似按钮),点击时展开
  • .automatic
    ——系统默认行为(全宽搜索字段)
行为
  • 类似于标签栏搜索模式
  • 节省工具栏空间
  • 搜索未使用时UI更简洁
用例
  • 列表/内容密集的屏幕
  • 拥挤的导航栏
  • 常规屏幕上的标签栏样式搜索
针对iOS 18+26的向后兼容包装器:
swift
extension View {
    @ViewBuilder func minimizedSearch() -> some View {
        if #available(iOS 26.0, *) {
            self.searchToolbarBehavior(.minimize)
        } else { self }
    }
}

// 使用方式
.searchable(text: $searchText)
.minimizedSearch()

searchPresentationToolbarBehavior (iOS 17.1+)

searchPresentationToolbarBehavior(iOS 17.1+)

Prevent title hiding during search

搜索期间防止标题隐藏

swift
.searchable(text: $searchText)
.searchPresentationToolbarBehavior(.avoidHidingContent)
Behavior
  • By default, navigation title hides when search becomes active
  • .avoidHidingContent
    keeps title visible during search
  • Maintains context while searching
Note This modifier was introduced in iOS 17.1, not iOS 26, but complements the new
searchToolbarBehavior
modifier.

swift
.searchable(text: $searchText)
.searchPresentationToolbarBehavior(.avoidHidingContent)
行为
  • 默认情况下,搜索激活时导航标题会隐藏
  • .avoidHidingContent
    在搜索期间保持标题可见
  • 搜索时保持上下文
注意 此修饰符在iOS 17.1中引入,而非iOS 26,但与新的
searchToolbarBehavior
修饰符互补。

iPad Enhancements

iPad增强

Menu Bar

菜单栏

Access common actions via swipe-down menu

通过向下滑动菜单访问常用操作

swift
.commands {
    TextEditingCommands() // Same API as macOS menu bar

    CommandGroup(after: .newItem) {
        Button("Add Note") {
            addNote()
        }
        .keyboardShortcut("n", modifiers: [.command, .shift])
    }
}
// Creates menu bar on iPad when people swipe down
swift
.commands {
    TextEditingCommands() // 与macOS菜单栏相同的API

    CommandGroup(after: .newItem) {
        Button("Add Note") {
            addNote()
        }
        .keyboardShortcut("n", modifiers: [.command, .shift])
    }
}
// 用户向下滑动时在iPad上创建菜单栏

Resizable Windows

可调整大小的窗口

Fluid resizing on iPad

iPad上的流畅调整大小

swift
// MIGRATION REQUIRED:
// Remove deprecated property list key in iPadOS 26:
// UIRequiresFullscreen (entire key deprecated, all values)

// For split view navigation, system automatically shows/hides columns
// based on available space during resize
NavigationSplitView {
    Sidebar()
} detail: {
    Detail()
}
// Adapts to resizing automatically
Reference "Elevate the design of your iPad app" (WWDC 2025)

swift
// 需要迁移:
// 在iPadOS 26中移除已弃用的属性列表键:
// UIRequiresFullscreen(整个键已弃用,所有值)

// 对于拆分视图导航,系统会根据调整大小时的可用空间自动显示/隐藏列
NavigationSplitView {
    Sidebar()
} detail: {
    Detail()
}
// 自动适应调整大小
参考 “提升iPad应用的设计”(WWDC 2025)

macOS Window Enhancements

macOS窗口增强

Synchronized Window Resize Animations

同步窗口调整大小动画

swift
.windowResizeAnchor(.topLeading) // Tailor where animation originates

// SwiftUI now synchronizes animation between content view size changes
// and window resizing - great for preserving continuity when switching tabs

swift
.windowResizeAnchor(.topLeading) // 定制动画起始位置

// SwiftUI现在同步内容视图大小变化与窗口调整大小之间的动画——非常适合切换标签页时保持连续性

Performance Improvements

性能改进

List Performance (macOS Focus)

列表性能(macOS重点)

Massive gains for large lists

大型列表的大幅提升

  • 6x faster loading for lists of 100,000+ items on macOS
  • 16x faster updates for large lists
  • Even bigger gains for larger lists
  • Improvements benefit all platforms (iOS, iPadOS, watchOS)
swift
List(trips) { trip in // 100k+ items
    TripRow(trip: trip)
}
// Loads 6x faster, updates 16x faster on macOS (iOS 26+)
  • macOS上100,000+项的列表加载速度快6倍
  • 大型列表更新速度快16倍
  • 列表越大,提升越明显
  • 改进惠及所有平台(iOS、iPadOS、watchOS)
swift
List(trips) { trip in // 100k+项
    TripRow(trip: trip)
}
// macOS上加载速度快6倍,更新速度快16倍(iOS 26+)

Scrolling Performance

滚动性能

Reduced dropped frames

减少掉帧

SwiftUI has improved scheduling of user interface updates on iOS and macOS. This improves responsiveness and lets SwiftUI do even more work to prepare for upcoming frames. All in all, it reduces the chance of your app dropping a frame while scrolling quickly at high frame rates.
SwiftUI改进了iOS和macOS上用户界面更新的调度。这提高了响应能力,并让SwiftUI能够做更多工作来准备即将到来的帧。总而言之,这降低了应用在高帧率下快速滚动时掉帧的可能性。

Nested ScrollViews with Lazy Stacks

带Lazy Stacks的嵌套ScrollViews

Photo carousels and multi-axis scrolling

照片轮播和多轴滚动

swift
ScrollView(.horizontal) {
    LazyHStack {
        ForEach(photoSets) { photoSet in
            ScrollView(.vertical) {
                LazyVStack {
                    ForEach(photoSet.photos) { photo in
                        PhotoView(photo: photo)
                    }
                }
            }
        }
    }
}
// Nested scrollviews now properly delay loading with lazy stacks
// Great for building photo carousels
swift
ScrollView(.horizontal) {
    LazyHStack {
        ForEach(photoSets) { photoSet in
            ScrollView(.vertical) {
                LazyVStack {
                    ForEach(photoSet.photos) { photo in
                        PhotoView(photo: photo)
                    }
                }
            }
        }
    }
}
// 嵌套ScrollView现在通过lazy stacks正确延迟加载
// 非常适合构建照片轮播

SwiftUI Performance Instrument

SwiftUI性能工具

New profiling tool in Xcode

Xcode中的新分析工具

Available lanes:
  • Long view body updates — Identify expensive body computations
  • Platform view updates — Track UIKit/AppKit bridging performance
  • Other performance problem areas
Reference "Optimize SwiftUI performance with instruments" (WWDC 2025)
Cross-reference SwiftUI Performance — Master the SwiftUI Instrument

可用通道:
  • 长视图body更新——识别昂贵的body计算
  • 平台视图更新——跟踪UIKit/AppKit桥接性能
  • 其他性能问题区域
参考 “使用工具优化SwiftUI性能”(WWDC 2025)
交叉参考 SwiftUI性能——掌握SwiftUI工具

Swift Concurrency Integration

Swift并发集成

Compile-Time Data Race Safety

编译时数据竞争安全

swift
@Observable
class TripStore {
    var trips: [Trip] = []

    func loadTrips() async {
        trips = await TripService.fetchTrips()
        // Swift 6 verifies data race safety at compile time
    }
}
Benefits Find bugs in concurrent code before they affect your app
swift
@Observable
class TripStore {
    var trips: [Trip] = []

    func loadTrips() async {
        trips = await TripService.fetchTrips()
        // Swift 6在编译时验证数据竞争安全
    }
}
优势 在并发代码影响应用之前发现bug

References

参考

  • "Embracing Swift concurrency" (WWDC 2025)
  • "Explore concurrency in SwiftUI" (WWDC 2025)
Cross-reference Swift Concurrency — Swift 6 strict concurrency patterns

  • “拥抱Swift并发”(WWDC 2025)
  • “探索SwiftUI中的并发”(WWDC 2025)
交叉参考 Swift并发——Swift 6严格并发模式

@Animatable Macro

@Animatable宏

Overview

概述

Simplifies custom animations by automatically synthesizing
animatableData
property.
通过自动合成
animatableData
属性简化自定义动画。

Before (@Animatable macro)

之前(@Animatable宏出现前)

swift
struct HikingRouteShape: Shape {
    var startPoint: CGPoint
    var endPoint: CGPoint
    var elevation: Double
    var drawingDirection: Bool // Don't want to animate this

    // Tedious manual animatableData declaration
    var animatableData: AnimatablePair<CGPoint.AnimatableData,
                        AnimatablePair<Double, CGPoint.AnimatableData>> {
        get {
            AnimatablePair(startPoint.animatableData,
                          AnimatablePair(elevation, endPoint.animatableData))
        }
        set {
            startPoint.animatableData = newValue.first
            elevation = newValue.second.first
            endPoint.animatableData = newValue.second.second
        }
    }
}
swift
struct HikingRouteShape: Shape {
    var startPoint: CGPoint
    var endPoint: CGPoint
    var elevation: Double
    var drawingDirection: Bool // 不想为此设置动画

    // 繁琐的手动animatableData声明
    var animatableData: AnimatablePair<CGPoint.AnimatableData,
                        AnimatablePair<Double, CGPoint.AnimatableData>> {
        get {
            AnimatablePair(startPoint.animatableData,
                          AnimatablePair(elevation, endPoint.animatableData))
        }
        set {
            startPoint.animatableData = newValue.first
            elevation = newValue.second.first
            endPoint.animatableData = newValue.second.second
        }
    }
}

After (@Animatable macro)

之后(使用@Animatable宏)

swift
@Animatable
struct HikingRouteShape: Shape {
    var startPoint: CGPoint
    var endPoint: CGPoint
    var elevation: Double

    @AnimatableIgnored
    var drawingDirection: Bool // Excluded from animation

    // animatableData automatically synthesized!
}
swift
@Animatable
struct HikingRouteShape: Shape {
    var startPoint: CGPoint
    var endPoint: CGPoint
    var elevation: Double

    @AnimatableIgnored
    var drawingDirection: Bool // 排除在动画之外

    // animatableData自动合成!
}

Key benefits

核心优势

  • Delete manual
    animatableData
    property
  • Use
    @AnimatableIgnored
    for properties to exclude
  • SwiftUI automatically synthesizes animation data
Cross-reference SwiftUI Animation (swiftui-animation-ref skill) — Comprehensive animation guide covering VectorArithmetic, Animatable protocol, @Animatable macro, animation types, Transaction system, and performance optimization

  • 删除手动
    animatableData
    属性
  • 使用
    @AnimatableIgnored
    标记要排除的属性
  • SwiftUI自动合成动画数据
交叉参考 SwiftUI动画(swiftui-animation-ref技能)——涵盖VectorArithmetic、Animatable协议、@Animatable宏、动画类型、Transaction系统和性能优化的综合动画指南

3D Spatial Layout (visionOS)

3D空间布局(visionOS)

Alignment3D

Alignment3D

Depth-based layout

基于深度的布局

swift
struct SunPositionView: View {
    @State private var timeOfDay: Double = 12.0

    var body: some View {
        HikingRouteView()
            .overlay(alignment: sunAlignment) {
                SunView()
                    .spatialOverlay(alignment: sunAlignment)
            }
    }

    var sunAlignment: Alignment3D {
        // Align sun in 3D space based on time of day
        Alignment3D(
            horizontal: .center,
            vertical: .top,
            depth: .back
        )
    }
}
swift
struct SunPositionView: View {
    @State private var timeOfDay: Double = 12.0

    var body: some View {
        HikingRouteView()
            .overlay(alignment: sunAlignment) {
                SunView()
                    .spatialOverlay(alignment: sunAlignment)
            }
    }

    var sunAlignment: Alignment3D {
        // 根据一天中的时间在3D空间中对齐太阳
        Alignment3D(
            horizontal: .center,
            vertical: .top,
            depth: .back
        )
    }
}

Manipulable Modifier

Manipulable修饰符

Interactive 3D objects

交互式3D对象

swift
Model3D(named: "WaterBottle")
    .manipulable() // People can pick up and move the object
swift
Model3D(named: "WaterBottle")
    .manipulable() // 用户可以拿起并移动对象

Surface Snapping APIs

表面吸附API

swift
@Environment(\.surfaceSnappingInfo) var snappingInfo: SurfaceSnappingInfo

var body: some View {
    VStackLayout().depthAlignment(.center) {
        Model3D(named: "waterBottle")
            .manipulable()

        Pedestal()
            .opacity(snappingInfo.classification == .table ? 1.0 : 0.0)
    }
}
swift
@Environment(\.surfaceSnappingInfo) var snappingInfo: SurfaceSnappingInfo

var body: some View {
    VStackLayout().depthAlignment(.center) {
        Model3D(named: "waterBottle")
            .manipulable()

        Pedestal()
            .opacity(snappingInfo.classification == .table ? 1.0 : 0.0)
    }
}

References

参考

  • "Meet SwiftUI spatial layout" (WWDC 2025)
  • "Set the scene with SwiftUI in visionOS" (WWDC 2025)
  • "What's new in visionOS" (WWDC 2025)

  • “认识SwiftUI空间布局”(WWDC 2025)
  • “在visionOS中使用SwiftUI设置场景”(WWDC 2025)
  • “visionOS新特性”(WWDC 2025)

Scene Bridging

场景桥接

Overview

概述

Scene bridging allows your UIKit and AppKit lifecycle apps to interoperate with SwiftUI scenes. Apps can use it to open SwiftUI-only scene types or use SwiftUI-exclusive features right from UIKit or AppKit code.
场景桥接允许你的UIKit和AppKit生命周期应用与SwiftUI场景互操作。应用可以使用它来打开仅支持SwiftUI的场景类型,或直接从UIKit或AppKit代码中使用SwiftUI独有的功能。

Supported Scene Types

支持的场景类型

From UIKit/AppKit apps, you can now use

从UIKit/AppKit应用中,你现在可以使用

  • MenuBarExtra
    (macOS)
  • ImmersiveSpace
    (visionOS)
  • RemoteImmersiveSpace
    (macOS → Vision Pro)
  • AssistiveAccess
    (iOS 26)
  • MenuBarExtra
    (macOS)
  • ImmersiveSpace
    (visionOS)
  • RemoteImmersiveSpace
    (macOS → Vision Pro)
  • AssistiveAccess
    (iOS 26)

Scene Modifiers

场景修饰符

Works with scene modifiers like:
  • .windowStyle()
  • .immersiveEnvironmentBehavior()
与场景修饰符一起使用,如:
  • .windowStyle()
  • .immersiveEnvironmentBehavior()

RemoteImmersiveSpace

RemoteImmersiveSpace

Mac app renders stereo content on Vision Pro

Mac应用在Vision Pro上渲染立体内容

swift
// In your macOS app
@main
struct MyMacApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }

        RemoteImmersiveSpace(id: "stereoView") {
            // Render stereo content on Apple Vision Pro
            // Uses CompositorServices
        }
    }
}
swift
// 在你的macOS应用中
@main
struct MyMacApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }

        RemoteImmersiveSpace(id: "stereoView") {
            // 在Apple Vision Pro上渲染立体内容
            // 使用CompositorServices
        }
    }
}

Features

特性

  • Mac app renders stereo content on Vision Pro
  • Hover effects and input events supported
  • Uses CompositorServices and Metal
Reference "What's new in Metal rendering for immersive apps" (WWDC 2025)
  • Mac应用在Vision Pro上渲染立体内容
  • 支持悬停效果和输入事件
  • 使用CompositorServices和Metal
参考 “沉浸式应用的Metal渲染新特性”(WWDC 2025)

AssistiveAccess Scene

AssistiveAccess场景

Special mode for users with cognitive disabilities

为认知障碍用户设计的特殊模式

swift
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }

        AssistiveAccessScene {
            SimplifiedUI() // UI shown when iPhone is in AssistiveAccess mode
        }
    }
}
Reference "Customize your app for Assistive Access" (WWDC 2025)

swift
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }

        AssistiveAccessScene {
            SimplifiedUI() // iPhone处于AssistiveAccess模式时显示的UI
        }
    }
}
参考 “为辅助访问自定义你的应用”(WWDC 2025)

AppKit Integration Enhancements

AppKit集成增强

SwiftUI Sheets in AppKit

AppKit中的SwiftUI Sheets

swift
// Show SwiftUI view in AppKit sheet
let hostingController = NSHostingController(rootView: SwiftUISettingsView())
presentAsSheet(hostingController)
// Great for incremental SwiftUI adoption
swift
// 在AppKit Sheet中显示SwiftUI视图
let hostingController = NSHostingController(rootView: SwiftUISettingsView())
presentAsSheet(hostingController)
// 非常适合逐步采用SwiftUI

NSGestureRecognizerRepresentable

NSGestureRecognizerRepresentable

swift
// Bridge AppKit gestures to SwiftUI
struct AppKitPanGesture: NSGestureRecognizerRepresentable {
    func makeNSGestureRecognizer(context: Context) -> NSPanGestureRecognizer {
        NSPanGestureRecognizer()
    }

    func updateNSGestureRecognizer(_ recognizer: NSPanGestureRecognizer, context: Context) {
        // Update configuration
    }
}
swift
// 将AppKit手势桥接到SwiftUI
struct AppKitPanGesture: NSGestureRecognizerRepresentable {
    func makeNSGestureRecognizer(context: Context) -> NSPanGestureRecognizer {
        NSPanGestureRecognizer()
    }

    func updateNSGestureRecognizer(_ recognizer: NSPanGestureRecognizer, context: Context) {
        // 更新配置
    }
}

NSHostingView in Interface Builder

Interface Builder中的NSHostingView

NSHostingView can now be used directly in Interface Builder for gradual SwiftUI adoption.

现在可以直接在Interface Builder中使用NSHostingView,逐步采用SwiftUI。

RealityKit Integration

RealityKit集成

Observable Entities

可观察实体

swift
@Observable
class RealityEntity {
    var position: SIMD3<Float>
    var rotation: simd_quatf
}

struct MyView: View {
    @State private var entity = RealityEntity()

    var body: some View {
        // SwiftUI views automatically observe changes
        Text("Position: \(entity.position.x)")
    }
}
swift
@Observable
class RealityEntity {
    var position: SIMD3<Float>
    var rotation: simd_quatf
}

struct MyView: View {
    @State private var entity = RealityEntity()

    var body: some View {
        // SwiftUI视图自动观察变化
        Text("Position: \(entity.position.x)")
    }
}

PresentationComponent

PresentationComponent

Present SwiftUI popovers, alerts, and sheets directly from RealityKit entities.
swift
// Present SwiftUI popovers from RealityKit entities
let popover = Entity()
mapEntity.addChild(popover)
popover.components[PresentationComponent.self] = PresentationComponent(
    isPresented: $popoverPresented,
    configuration: .popover(arrowEdge: .bottom),
    content: DetailsView()
)
直接从RealityKit实体呈现SwiftUI弹出框、警报和Sheet。
swift
// 从RealityKit实体呈现SwiftUI弹出框
let popover = Entity()
mapEntity.addChild(popover)
popover.components[PresentationComponent.self] = PresentationComponent(
    isPresented: $popoverPresented,
    configuration: .popover(arrowEdge: .bottom),
    content: DetailsView()
)

Additional Improvements

其他改进

  • ViewAttachmentComponent
    — add SwiftUI views to entities
  • GestureComponent
    — entity touch and gesture responsiveness
  • Enhanced coordinate conversion API
  • Synchronizing animations, binding to components
  • New sizing behaviors for RealityView
Reference "Better Together: SwiftUI & RealityKit" (WWDC 2025)

  • ViewAttachmentComponent
    ——将SwiftUI视图添加到实体
  • GestureComponent
    ——实体触摸和手势响应
  • 增强的坐标转换API
  • 同步动画、绑定到组件
  • RealityView的新尺寸行为
参考 “更好的组合:SwiftUI & RealityKit”(WWDC 2025)

WebView & WebPage

WebView & WebPage

Overview

概述

WebKit now provides full SwiftUI APIs for embedding web content, eliminating the need to drop down to UIKit.
WebKit现在提供用于嵌入网页内容的完整SwiftUI API,无需降级到UIKit。

WebView

WebView

Display web content

显示网页内容

swift
import WebKit

struct ArticleView: View {
    let articleURL: URL

    var body: some View {
        WebView(url: articleURL)
    }
}
swift
import WebKit

struct ArticleView: View {
    let articleURL: URL

    var body: some View {
        WebView(url: articleURL)
    }
}

WebPage (Observable Model)

WebPage(可观察模型)

Rich interaction with web content

与网页内容的丰富交互

swift
import WebKit

struct InAppBrowser: View {
    @State private var page = WebPage()

    var body: some View {
        VStack {
            Text(page.title ?? "Loading...")

            WebView(page)
                .ignoresSafeArea()
                .onAppear {
                    page.load(URLRequest(url: articleURL))
                }

            HStack {
                Button("Back") { page.goBack() }
                    .disabled(!page.canGoBack)
                Button("Forward") { page.goForward() }
                    .disabled(!page.canGoForward)
            }
        }
    }
}
swift
import WebKit

struct InAppBrowser: View {
    @State private var page = WebPage()

    var body: some View {
        VStack {
            Text(page.title ?? "Loading...")

            WebView(page)
                .ignoresSafeArea()
                .onAppear {
                    page.load(URLRequest(url: articleURL))
                }

            HStack {
                Button("Back") { page.goBack() }
                    .disabled(!page.canGoBack)
                Button("Forward") { page.goForward() }
                    .disabled(!page.canGoForward)
            }
        }
    }
}

WebPage features

WebPage特性

  • Programmatic navigation (
    goBack()
    ,
    goForward()
    )
  • Access page properties (
    title
    ,
    url
    ,
    canGoBack
    ,
    canGoForward
    )
  • Observable — SwiftUI views update automatically
  • 程序化导航(
    goBack()
    goForward()
  • 访问页面属性(
    title
    url
    canGoBack
    canGoForward
  • 可观察——SwiftUI视图自动更新

Advanced WebKit Features

高级WebKit特性

  • Custom user agents
  • JavaScript execution
  • Custom URL schemes
  • And more
Reference "Meet WebKit for SwiftUI" (WWDC 2025)

  • 自定义用户代理
  • JavaScript执行
  • 自定义URL方案
  • 等等
参考 “认识SwiftUI的WebKit”(WWDC 2025)

TextEditor with AttributedString

带AttributedString的TextEditor

Overview

概述

SwiftUI's new support for rich text editing is great for experiences like commenting on photos. TextView now supports AttributedString!
Note The WWDC transcript uses "TextView" as editorial language. The actual SwiftUI API is
TextEditor
which now supports
AttributedString
binding for rich text editing.
SwiftUI对富文本编辑的新支持非常适合照片评论等体验。TextView现在支持AttributedString!
注意 WWDC文稿中使用“TextView”作为编辑术语。实际SwiftUI API是
TextEditor
,现在支持
AttributedString
绑定以进行富文本编辑。

Rich Text Editing

富文本编辑

swift
struct CommentView: View {
    @State private var comment = AttributedString("Enter your comment")

    var body: some View {
        TextEditor(text: $comment)
            // Built-in text formatting controls included
            // Users can apply bold, italic, underline, etc.
    }
}
swift
struct CommentView: View {
    @State private var comment = AttributedString("Enter your comment")

    var body: some View {
        TextEditor(text: $comment)
            // 包含内置文本格式控件
            // 用户可以应用粗体、斜体、下划线等
    }
}

Features

特性

  • Built-in text formatting controls (bold, italic, underline, colors, etc.)
  • Binding to
    AttributedString
    preserves formatting
  • Automatic toolbar with formatting options
  • 内置文本格式控件(粗体、斜体、下划线、颜色等)
  • 绑定到
    AttributedString
    以保留格式
  • 带格式选项的自动工具栏

Advanced AttributedString Features

高级AttributedString特性

Customization options

自定义选项

  • Paragraph styles
  • Attribute transformations
  • Constrain which attributes users can apply
Reference "Cook up a rich text experience in SwiftUI with AttributedString" (WWDC 2025)
Cross-reference App Intents Integration (app-intents-ref skill) — AttributedString for Apple Intelligence Use Model action

  • 段落样式
  • 属性转换
  • 限制用户可以应用的属性
参考 “使用AttributedString在SwiftUI中打造富文本体验”(WWDC 2025)
交叉参考 App Intents集成(app-intents-ref技能)——Apple Intelligence使用模型操作的AttributedString

Drag and Drop Enhancements

拖放增强

Multiple Item Dragging

多项目拖放

Drag multiple items based on selection

根据选择拖放多个项目

swift
struct PhotoGrid: View {
    @State private var selectedPhotos: [Photo.ID] = []

    var body: some View {
        ScrollView {
            LazyVGrid(columns: gridColumns) {
                ForEach(model.photos) { photo in
                    view(photo: photo)
                        .draggable(containerItemID: photo.id)
                }
            }
        }
        .dragContainer(for: Photo.self, selection: selectedPhotos) { draggedIDs in
            photos(ids: draggedIDs)
        }
    }
}
Key APIs:
  • .draggable(containerItemID:containerNamespace:)
    marks each item as part of a drag container (namespace defaults to
    nil
    )
  • .dragContainer(for:selection:)
    provides the typed items lazily when a drop occurs
swift
struct PhotoGrid: View {
    @State private var selectedPhotos: [Photo.ID] = []

    var body: some View {
        ScrollView {
            LazyVGrid(columns: gridColumns) {
                ForEach(model.photos) { photo in
                    view(photo: photo)
                        .draggable(containerItemID: photo.id)
                }
            }
        }
        .dragContainer(for: Photo.self, selection: selectedPhotos) { draggedIDs in
            photos(ids: draggedIDs)
        }
    }
}
核心API:
  • .draggable(containerItemID:containerNamespace:)
    标记每个项为拖放容器的一部分(命名空间默认为
    nil
  • .dragContainer(for:selection:)
    在拖放发生时延迟提供类型化项

DragConfiguration

DragConfiguration

Customize supported operations

自定义支持的操作

swift
.dragConfiguration(DragConfiguration(allowMove: false, allowDelete: true))
swift
.dragConfiguration(DragConfiguration(allowMove: false, allowDelete: true))

Observing Drag Events

观察拖放事件

swift
.onDragSessionUpdated { session in
    let ids = session.draggedItemIDs(for: Photo.ID.self)
    if session.phase == .ended(.delete) {
        trash(ids)
        deletePhotos(ids)
    }
}
swift
.onDragSessionUpdated { session in
    let ids = session.draggedItemIDs(for: Photo.ID.self)
    if session.phase == .ended(.delete) {
        trash(ids)
        deletePhotos(ids)
    }
}

Drag Preview Formations

拖放预览布局

swift
.dragPreviewsFormation(.stack) // Items stack nicely on top of one another

// Other formations:
// - .default
// - .grid
// - .stack
swift
.dragPreviewsFormation(.stack) // 项整齐堆叠在彼此之上

// 其他布局:
// - .default
// - .grid
// - .stack

Complete Example

完整示例

swift
struct PhotoLibrary: View {
    @State private var selectedPhotos: [Photo.ID] = []

    var body: some View {
        ScrollView {
            LazyVGrid(columns: gridColumns) {
                ForEach(model.photos) { photo in
                    view(photo: photo)
                        .draggable(containerItemID: photo.id)
                }
            }
        }
        .dragContainer(for: Photo.self, selection: selectedPhotos) { draggedIDs in
            photos(ids: draggedIDs)
        }
        .dragConfiguration(DragConfiguration(allowMove: false, allowDelete: true))
        .dragPreviewsFormation(.stack)
        .onDragSessionUpdated { session in
            let ids = session.draggedItemIDs(for: Photo.ID.self)
            if session.phase == .ended(.delete) {
                trash(ids)
                deletePhotos(ids)
            }
        }
    }
}

swift
struct PhotoLibrary: View {
    @State private var selectedPhotos: [Photo.ID] = []

    var body: some View {
        ScrollView {
            LazyVGrid(columns: gridColumns) {
                ForEach(model.photos) { photo in
                    view(photo: photo)
                        .draggable(containerItemID: photo.id)
                }
            }
        }
        .dragContainer(for: Photo.self, selection: selectedPhotos) { draggedIDs in
            photos(ids: draggedIDs)
        }
        .dragConfiguration(DragConfiguration(allowMove: false, allowDelete: true))
        .dragPreviewsFormation(.stack)
        .onDragSessionUpdated { session in
            let ids = session.draggedItemIDs(for: Photo.ID.self)
            if session.phase == .ended(.delete) {
                trash(ids)
                deletePhotos(ids)
            }
        }
    }
}

3D Charts

3D图表

Overview

概述

Swift Charts now supports three-dimensional plotting with
Chart3D
.
Swift Charts现在支持使用
Chart3D
进行三维绘图。

Basic Usage

基础用法

From WWDC 256:21:35

来自WWDC 256:21:35

swift
import Charts

struct HikePlotView: View {
    var body: some View {
        Chart3D {
            SurfacePlot(x: "x", y: "y", z: "z") { x, y in
                sin(x) * cos(y)
            }
            .foregroundStyle(Gradient(colors: [.orange, .pink]))
        }
        .chartXScale(domain: -3...3)
        .chartYScale(domain: -3...3)
        .chartZScale(domain: -3...3)
    }
}
swift
import Charts

struct HikePlotView: View {
    var body: some View {
        Chart3D {
            SurfacePlot(x: "x", y: "y", z: "z") { x, y in
                sin(x) * cos(y)
            }
            .foregroundStyle(Gradient(colors: [.orange, .pink]))
        }
        .chartXScale(domain: -3...3)
        .chartYScale(domain: -3...3)
        .chartZScale(domain: -3...3)
    }
}

Features

特性

  • Chart3D
    container
  • SurfacePlot
    for continuous surface rendering from a function
  • Z-axis specific modifiers (
    .chartZScale()
    ,
    .chartZAxis()
    , etc.)
  • All existing chart marks with 3D variants (e.g.,
    LineMark3D
    )
Reference "Bring Swift Charts to the third dimension" (WWDC 2025)

  • Chart3D
    容器
  • SurfacePlot
    用于从函数渲染连续表面
  • Z轴特定修饰符(
    .chartZScale()
    .chartZAxis()
    等)
  • 所有现有图表标记的3D变体(如
    LineMark3D
参考 “将Swift Charts带入三维空间”(WWDC 2025)

Widgets & Controls

小组件与控件

Controls on watchOS and macOS

watchOS和macOS上的控件

watchOS 26

watchOS 26

swift
struct FavoriteLocationControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "FavoriteLocation") {
            ControlWidgetButton(action: MarkFavoriteIntent()) {
                Label("Mark Favorite", systemImage: "star")
            }
        }
    }
}
// Access from watch face or Shortcuts
swift
struct FavoriteLocationControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "FavoriteLocation") {
            ControlWidgetButton(action: MarkFavoriteIntent()) {
                Label("Mark Favorite", systemImage: "star")
            }
        }
    }
}
// 从表盘或快捷指令访问

macOS

macOS

Controls now appear in Control Center on Mac.
控件现在出现在Mac的控制中心。

Widgets on visionOS

visionOS上的小组件

Level of detail customization

细节级别自定义

swift
struct CountdownWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "Countdown") { entry in
            CountdownView(entry: entry)
        }
    }
}

struct PhotoCountdownView: View {
    @Environment(\.levelOfDetail) var levelOfDetail: LevelOfDetail

    var body: some View {
        switch levelOfDetail {
        case .default:
            RecentPhotosView() // Full detail when close
        case .simplified:
            CountdownView()   // Simplified when further away
        default:
            CountdownView()
        }
    }
}
swift
struct CountdownWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: "Countdown") { entry in
            CountdownView(entry: entry)
        }
    }
}

struct PhotoCountdownView: View {
    @Environment(\.levelOfDetail) var levelOfDetail: LevelOfDetail

    var body: some View {
        switch levelOfDetail {
        case .default:
            RecentPhotosView() // 靠近时显示完整细节
        case .simplified:
            CountdownView()   // 远离时显示简化版本
        default:
            CountdownView()
        }
    }
}

Widgets on CarPlay

CarPlay上的小组件

Live Activities on CarPlay

CarPlay上的Live Activities

Live Activities now appear on CarPlay displays for glanceable information while driving.
Live Activities现在出现在CarPlay显示屏上,供驾驶时快速查看信息。

Additional Widget Features

其他小组件特性

  • Push-based updating API
  • New relevance APIs for watchOS
Reference "What's new in widgets" (WWDC 2025)

  • 推送式更新API
  • watchOS的新相关性API
参考 “小组件新特性”(WWDC 2025)

Migration Checklist

迁移清单

Deprecated APIs

已弃用API

❌ Remove in iPadOS 26

❌ 在iPadOS 26中移除

xml
<key>UIRequiresFullscreen</key>
<!-- Entire property list key is deprecated (all values) -->
Apps must support resizable windows on iPad.
xml
<key>UIRequiresFullscreen</key>
<!-- 整个属性列表键已弃用(所有值) -->
应用必须支持iPad上的可调整大小窗口。

Automatic Adoptions (Recompile Only)

自动采用(仅需重新编译)

✅ Liquid Glass design for navigation, tab bars, toolbars ✅ Bottom-aligned search on iPhone ✅ List performance improvements (6x loading, 16x updating) ✅ Scrolling performance improvements ✅ System controls (toggles, pickers, sliders) new appearance ✅ Bordered buttons default to capsule shape ✅ Updated control heights (slightly taller on macOS) ✅ Monochrome icon rendering in toolbars ✅ Menus: icons on leading edge, consistent across iOS and macOS ✅ Sheets morph out of dialogs automatically ✅ Scroll edge blur/fade under system toolbars
✅ 导航、标签栏、工具栏的Liquid Glass设计 ✅ iPhone上的底部对齐搜索 ✅ 列表性能改进(加载快6倍,更新快16倍) ✅ 滚动性能改进 ✅ 系统控件(开关、选择器、滑块)新外观 ✅ 带边框按钮默认采用胶囊形状 ✅ 更新的控件高度(macOS上略高) ✅ 工具栏中的单色图标渲染 ✅ 菜单:图标位于左侧,iOS和macOS上一致 ✅ Sheets自动从对话框变形 ✅ 系统工具栏下的滚动边缘模糊/淡出

Audit Items (Remove Old Customizations)

审计项(移除旧自定义)

⚠️ Remove
presentationBackground
from sheets (let Liquid Glass material shine) ⚠️ Remove extra backgrounds/darkening effects behind toolbar areas ⚠️ Remove hard-coded control heights (use automatic sizing) ⚠️ Update section headers to title-style capitalization (no longer auto-uppercased)
⚠️ 从Sheets中移除
presentationBackground
(让Liquid Glass材质发挥作用) ⚠️ 移除工具栏区域后的额外背景/变暗效果 ⚠️ 移除硬编码控件高度(使用自动尺寸) ⚠️ 更新节标题为标题式大写(不再自动大写)

Manual Adoptions (Code Changes)

手动采用(代码更改)

🔧 Toolbar spacers (
.fixed
) 🔧 Tinted prominent buttons in toolbars 🔧 Glass effect for custom views (
.glassEffect()
) 🔧
glassEffectID
for morphing transitions between glass elements 🔧
GlassEffectContainer
for multiple nearby glass elements 🔧
sharedBackgroundVisibility(.hidden)
to remove toolbar item from group background 🔧 Sheet morphing from buttons (
navigationZoomTransition
) 🔧 Search tab role (
Tab(role: .search)
) 🔧 Compact search toolbar (
.searchToolbarBehavior(.minimize)
) 🔧 Extra large buttons (
.controlSize(.extraLarge)
) 🔧 Concentric rectangle shape (
.containerConcentric
) 🔧 iPad menu bar (
.commands
) 🔧 Window resize anchor (
.windowResizeAnchor()
) 🔧 @Animatable macro for custom shapes/modifiers 🔧 WebView for web content 🔧 TextEditor with AttributedString binding 🔧 Enhanced drag and drop with
.dragContainer
🔧 Slider ticks (
SliderTick
,
SliderTickContentForEach
) 🔧 Slider thumb visibility (
.sliderThumbVisibility()
) 🔧 Safe area bars with blur (
.safeAreaBar()
+
.scrollEdgeEffectStyle()
) 🔧 In-app URL opening (
openURL(url, prefersInApp: true)
) 🔧 Close and confirm button roles (
Button(role: .close)
) 🔧 Glass button styles (
GlassButtonStyle
— iOS 26.1+) 🔧 Button sizing control (
.buttonSizing()
) 🔧 Toolbar morphing transitions (per-view
.toolbar {}
inside NavigationStack) 🔧 DefaultToolbarItem for system components in toolbars 🔧 Stable toolbar items (
toolbar(id:)
with matched IDs across screens) 🔧 User-customizable toolbars (
toolbar(id:)
with
CustomizableToolbarContent
) 🔧 Tab bar minimization (
.tabBarMinimizeBehavior(.onScrollDown)
) 🔧 Tab view bottom accessory (
.tabViewBottomAccessory(isEnabled:content:)
— iOS 26.1+)

🔧 工具栏间隔器(
.fixed
) 🔧 工具栏中的着色突出按钮 🔧 自定义视图的玻璃效果(
.glassEffect()
) 🔧 用于玻璃元素之间变形过渡的
glassEffectID
🔧 用于多个邻近玻璃元素的
GlassEffectContainer
🔧
sharedBackgroundVisibility(.hidden)
从组背景中移除工具栏项 🔧 从按钮变形的Sheet(
navigationZoomTransition
) 🔧 搜索标签页角色(
Tab(role: .search)
) 🔧 紧凑搜索工具栏(
.searchToolbarBehavior(.minimize)
) 🔧 超大按钮(
.controlSize(.extraLarge)
) 🔧 同心矩形形状(
.containerConcentric
) 🔧 iPad菜单栏(
.commands
) 🔧 窗口调整大小锚点(
.windowResizeAnchor()
) 🔧 用于自定义形状/修饰符的@Animatable宏 🔧 用于网页内容的WebView 🔧 带AttributedString绑定的TextEditor 🔧 带
.dragContainer
的增强拖放 🔧 滑块刻度(
SliderTick
SliderTickContentForEach
) 🔧 滑块thumb可见性(
.sliderThumbVisibility()
) 🔧 带模糊的安全区域栏(
.safeAreaBar()
+
.scrollEdgeEffectStyle()
) 🔧 应用内URL打开(
openURL(url, prefersInApp: true)
) 🔧 关闭和确认按钮角色(
Button(role: .close)
) 🔧 玻璃按钮样式(
GlassButtonStyle
— iOS 26.1+) 🔧 按钮尺寸控制(
.buttonSizing()
) 🔧 工具栏变形过渡(NavigationStack内每个视图的
.toolbar {}
) 🔧 用于工具栏中系统组件的DefaultToolbarItem 🔧 稳定工具栏项(跨屏幕使用匹配ID的
toolbar(id:)
) 🔧 用户可自定义工具栏(带
CustomizableToolbarContent
toolbar(id:)
) 🔧 标签栏最小化(
.tabBarMinimizeBehavior(.onScrollDown)
) 🔧 标签视图底部附件(
.tabViewBottomAccessory(isEnabled:content:)
— iOS 26.1+)

Best Practices

最佳实践

Performance

性能

DO

建议

  • Profile with new SwiftUI performance instrument
  • Use lazy stacks in nested ScrollViews
  • Trust automatic list performance improvements
  • 使用新SwiftUI性能工具进行分析
  • 在嵌套ScrollViews中使用lazy stacks
  • 信任自动列表性能改进

DON'T

不建议

  • Over-optimize - let framework improvements help first
  • Ignore long view body updates in profiler
  • 过度优化——先让框架改进发挥作用
  • 忽略分析器中的长视图body更新

Liquid Glass Design

Liquid Glass设计

DO

建议

  • Recompile and test automatic appearance
  • Use toolbar spacers for logical grouping
  • Apply glass effect to custom views that benefit from reflections
  • Attach toolbars to individual views for automatic morphing transitions
  • Use
    toolbar(id:)
    with matching IDs for items that should stay stable across screens
  • 重新编译并测试自动外观
  • 使用工具栏间隔器进行逻辑分组
  • 将玻璃效果应用于可从反射中受益的自定义视图
  • 将工具栏附加到各个视图以实现自动变形过渡
  • 对跨屏幕应保持稳定的项使用带匹配ID的
    toolbar(id:)

DON'T

不建议

  • Fight the automatic design - embrace consistency
  • Over-tint toolbars (use for prominence only, not decoration)
  • Attach the toolbar to NavigationStack and expect morphing (attach to views inside it)
  • Apply
    presentationBackground
    to sheets (remove it for Liquid Glass material)
  • Place nearby glass elements in separate containers (use
    GlassEffectContainer
    )
  • Add extra backgrounds or darkening effects behind system toolbar areas
  • 抗拒自动设计——拥抱一致性
  • 过度着色工具栏(仅用于突出显示,而非装饰)
  • 将工具栏附加到NavigationStack并期望变形(附加到其中的视图)
  • 为Sheets应用
    presentationBackground
    (移除它以使用Liquid Glass材质)
  • 将邻近玻璃元素放在单独容器中(使用
    GlassEffectContainer
  • 在系统工具栏区域后添加额外背景或变暗效果

Layout & Spacing

布局与间距

DO

建议

  • Use
    .safeAreaPadding()
    for edge-to-edge content (iOS 17+)
  • Combine
    .safeAreaPadding()
    with Liquid Glass materials extending edge-to-edge
  • Use
    .padding()
    for internal spacing between views
  • 对边缘到边缘内容使用
    .safeAreaPadding()
    (iOS 17+)
  • .safeAreaPadding()
    与边缘到边缘的Liquid Glass材质结合使用
  • 对视图之间的内部间距使用
    .padding()

DON'T

不建议

  • Use
    .padding()
    when content extends to screen edges (ignores notch/home indicator)
  • Manually calculate safe area insets with GeometryReader on iOS 17+ (use
    .safeAreaPadding()
    instead)
Reference: See
axiom-swiftui-layout-ref
skill for complete
.safeAreaPadding()
vs
.padding()
guide, or
axiom-liquid-glass-ref
for Liquid Glass-specific safe area patterns.
  • 内容延伸到屏幕边缘时使用
    .padding()
    (忽略刘海/主屏幕指示器)
  • 在iOS 17+上使用GeometryReader手动计算安全区域插入(改用
    .safeAreaPadding()
参考 有关
.safeAreaPadding()
.padding()
的完整指南,请参阅
axiom-swiftui-layout-ref
技能,或有关Liquid Glass特定安全区域模式的
axiom-liquid-glass-ref

Rich Text

富文本

DO

建议

  • Use
    AttributedString
    binding for
    TextEditor
  • Constrain attributes if needed for your use case
  • Consider localization with rich text
  • TextEditor
    使用
    AttributedString
    绑定
  • 根据用例限制属性
  • 考虑富文本的本地化

DON'T

不建议

  • Use plain
    String
    and lose formatting
  • Allow all attributes without considering UX
  • 使用普通
    String
    并丢失格式
  • 允许所有属性而不考虑用户体验

Spatial Layout (visionOS)

空间布局(visionOS)

DO

建议

  • Use
    Alignment3D
    for depth-based layouts
  • Enable
    .manipulable()
    for objects users should interact with
  • Check surface snapping state for context-aware UI
  • 对基于深度的布局使用
    Alignment3D
  • 为用户应交互的对象启用
    .manipulable()
  • 检查表面吸附状态以实现上下文感知UI

DON'T

不建议

  • Use 2D alignment APIs for 3D layouts
  • Make all objects manipulable (only what makes sense)

  • 对3D布局使用2D对齐API
  • 使所有对象可操作(仅对有意义的对象使用)

Troubleshooting

故障排除

Issue: Liquid Glass appearance not showing

问题:Liquid Glass外观未显示

Symptom App still has old design after updating to iOS 26 SDK
症状 更新到iOS 26 SDK后应用仍具有旧设计

Solution

解决方案

  1. Clean build folder (Shift-Cmd-K)
  2. Rebuild with Xcode 16+ targeting iOS 26 SDK
  3. Check deployment target is iOS 26+
  1. 清理构建文件夹(Shift-Cmd-K)
  2. 使用Xcode 16+重新构建,目标为iOS 26 SDK
  3. 检查部署目标为iOS 26+

Issue: Bottom-aligned search not appearing on iPhone

问题:iPhone上未显示底部对齐搜索

Symptom Search remains at top on iPhone
症状 搜索在iPhone上仍位于顶部

Solution

解决方案

swift
// ✅ CORRECT: searchable on NavigationSplitView
NavigationSplitView {
    List { }
        .searchable(text: $query)
}

// ❌ WRONG: searchable on List directly in non-navigation context
List { }
    .searchable(text: $query)
swift
// ✅ 正确:searchable在NavigationSplitView上
NavigationSplitView {
    List { }
        .searchable(text: $query)
}

// ❌ 错误:searchable直接在非导航上下文的List上
List { }
    .searchable(text: $query)

Issue: @Animatable macro not synthesizing animatableData

问题:@Animatable宏未合成animatableData

Symptom Compile error "Type does not conform to Animatable"
症状 编译错误“类型不符合Animatable”

Solution

解决方案

swift
// Ensure all properties are either:
// 1. VectorArithmetic conforming types (Double, CGFloat, CGPoint, etc.)
// 2. Marked with @AnimatableIgnored

@Animatable
struct MyShape: Shape {
    var radius: Double // ✅ VectorArithmetic
    var position: CGPoint // ✅ VectorArithmetic

    @AnimatableIgnored
    var fillColor: Color // ✅ Ignored (Color is not VectorArithmetic)
}
swift
// 确保所有属性要么:
// 1. 符合VectorArithmetic类型(Double、CGFloat、CGPoint等)
// 2. 标记为@AnimatableIgnored

@Animatable
struct MyShape: Shape {
    var radius: Double // ✅ 符合VectorArithmetic
    var position: CGPoint // ✅ 符合VectorArithmetic

    @AnimatableIgnored
    var fillColor: Color // ✅ 被忽略(Color不符合VectorArithmetic)
}

Issue: AttributedString formatting lost in TextEditor

问题:TextEditor中AttributedString格式丢失

Symptom Rich text formatting disappears
症状 富文本格式消失

Solution

解决方案

swift
// ✅ CORRECT: Binding to AttributedString
@State private var text = AttributedString("Hello")
TextEditor(text: $text)

// ❌ WRONG: Binding to String
@State private var text = "Hello"
TextEditor(text: $text) // Plain String loses formatting
swift
// ✅ 正确:绑定到AttributedString
@State private var text = AttributedString("Hello")
TextEditor(text: $text)

// ❌ 错误:绑定到String
@State private var text = "Hello"
TextEditor(text: $text) // 普通String会丢失格式

Issue: Drag and drop delete not working

问题:拖放删除不起作用

Symptom Dragging to Dock trash doesn't delete items
症状 拖到程序坞废纸篓不会删除项目

Solution

解决方案

swift
// Must enable delete in drag configuration
.dragConfiguration(DragConfiguration(allowMove: false, allowDelete: true))

// And observe the delete event
.onDragSessionUpdated { session in
    if session.phase == .ended(.delete) {
        deleteItems()
    }
}
swift
// 必须在拖放配置中启用删除
.dragConfiguration(DragConfiguration(allowMove: false, allowDelete: true))

// 并观察删除事件
.onDragSessionUpdated { session in
    if session.phase == .ended(.delete) {
        deleteItems()
    }
}

Issue: SliderTickContentForEach won't compile with custom structs

问题:SliderTickContentForEach与自定义结构体一起使用时无法编译

Symptom Compile error when iterating over custom types like
[Chapter]
swift
// ERROR: Cannot convert value of type 'Chapter' to expected argument type
SliderTickContentForEach(chapters, id: \.id) { chapter in
    SliderTick(chapter.time) { ... }
}
症状 遍历自定义类型如
[Chapter]
时编译错误
swift
// 错误:无法将类型'Chapter'的值转换为预期参数类型
SliderTickContentForEach(chapters, id: \.id) { chapter in
    SliderTick(chapter.time) { ... }
}

Solution

解决方案

SliderTickContentForEach
requires
Data.Element
to match the
SliderTick<V>
value type. Extract the numeric values and look up metadata separately:
swift
// ✅ CORRECT: Iterate over Double values
SliderTickContentForEach(chapters.map(\.time), id: \.self) { time in
    SliderTick(time) {
        if let chapter = chapters.first(where: { $0.time == time }) {
            Text(chapter.name)
        }
    }
}
Why The API enforces type safety between tick positions and slider values. This is an API design constraint, not a bug.
SliderTickContentForEach
要求
Data.Element
SliderTick<V>
值类型匹配。提取数值并单独查找元数据:
swift
// ✅ 正确:遍历Double值
SliderTickContentForEach(chapters.map(\.time), id: \.self) { time in
    SliderTick(time) {
        if let chapter = chapters.first(where: { $0.time == time }) {
            Text(chapter.name)
        }
    }
}
原因 API强制刻度位置与滑块值之间的类型安全。这是API设计约束,而非bug。

Issue: Toolbar morphing not working

问题:工具栏变形不起作用

Symptom Toolbar stays static during NavigationStack push/pop — no morphing animation
症状 工具栏在NavigationStack推送/弹出期间保持静态——无变形动画

Solution

解决方案

swift
// ❌ WRONG: Toolbar on NavigationStack — nothing to morph between
NavigationStack {
    ContentView()
}
.toolbar {
    ToolbarItem { Button("Action") { } }
}

// ✅ CORRECT: Toolbar on each destination view — iOS 26 morphs between them
NavigationStack {
    ListView()
        .toolbar {
            ToolbarItem { Button("Filter") { } }
        }
        .navigationDestination(for: Item.self) { item in
            DetailView(item: item)
                .toolbar {
                    ToolbarItem { Button("Edit") { } }
                }
        }
}
Move
.toolbar {}
from the NavigationStack to each view inside it. iOS 26 morphs between per-view toolbars during navigation transitions.

swift
// ❌ 错误:工具栏在NavigationStack上——无可变形内容
NavigationStack {
    ContentView()
}
.toolbar {
    ToolbarItem { Button("Action") { } }
}

// ✅ 正确:工具栏在每个目标视图上——iOS 26在它们之间变形
NavigationStack {
    ListView()
        .toolbar {
            ToolbarItem { Button("Filter") { } }
        }
        .navigationDestination(for: Item.self) { item in
            DetailView(item: item)
                .toolbar {
                    ToolbarItem { Button("Edit") { } }
                }
        }
}
.toolbar {}
从NavigationStack移至其中的每个视图。iOS 26在导航过渡期间在每个视图的工具栏之间变形。

Resources

资源

WWDC: 2025-256, 2025-278 (What's new in widgets), 2025-287 (Meet WebKit for SwiftUI), 2025-310 (Optimize SwiftUI performance with instruments), 2025-323 (Build a SwiftUI app with the new design), 2025-325 (Bring Swift Charts to the third dimension), 2025-341 (Cook up a rich text experience in SwiftUI with AttributedString)
Docs: /swiftui, /swiftui/defaulttoolbaritem, /swiftui/toolbarspacer, /swiftui/searchtoolbarbehavior, /swiftui/view/toolbar(id:content:), /swiftui/view/tabbarminimizebehavior(_:), /swiftui/view/tabviewbottomaccessory(isenabled:content:), /swiftui/slider, /swiftui/slidertick, /swiftui/slidertickcontentforeach, /webkit, /foundation/attributedstring, /charts, /realitykit/presentationcomponent, /swiftui/chart3d
Skills: axiom-swiftui-performance, axiom-liquid-glass, axiom-swift-concurrency, axiom-app-intents-ref, axiom-swiftui-search-ref

Primary source WWDC 2025-256 "What's new in SwiftUI". Additional content from 2025-323 (Build a SwiftUI app with the new design), 2025-287 (Meet WebKit for SwiftUI), and Apple documentation. Version iOS 26+, iPadOS 26+, macOS Tahoe+, watchOS 26+, visionOS 26+
WWDC: 2025-256, 2025-278(小组件新特性), 2025-287(认识SwiftUI的WebKit), 2025-310(使用工具优化SwiftUI性能), 2025-323(使用新设计构建SwiftUI应用), 2025-325(将Swift Charts带入三维空间), 2025-341(使用AttributedString在SwiftUI中打造富文本体验)
文档: /swiftui, /swiftui/defaulttoolbaritem, /swiftui/toolbarspacer, /swiftui/searchtoolbarbehavior, /swiftui/view/toolbar(id:content:), /swiftui/view/tabbarminimizebehavior(_:), /swiftui/view/tabviewbottomaccessory(isenabled:content:), /swiftui/slider, /swiftui/slidertick, /swiftui/slidertickcontentforeach, /webkit, /foundation/attributedstring, /charts, /realitykit/presentationcomponent, /swiftui/chart3d
技能: axiom-swiftui-performance, axiom-liquid-glass, axiom-swift-concurrency, axiom-app-intents-ref, axiom-swiftui-search-ref

主要来源 WWDC 2025-256 “SwiftUI新特性”。额外内容来自2025-323(使用新设计构建SwiftUI应用)、2025-287(认识SwiftUI的WebKit)和Apple文档。 版本 iOS 26+, iPadOS 26+, macOS Tahoe+, watchOS 26+, visionOS 26+