WWDC Quick Look 💓 By SwiftGGTeam
Inspectors in SwiftUI: Discover the details

Inspectors in SwiftUI: Discover the details

观看原视频

Highlight

SwiftUI 在 iOS 17、iPadOS 17 和 macOS Sonoma 中新增 inspector 修饰符,让开发者用一行代码为应用添加自适应的详情面板:在宽屏下显示为右侧边栏,在紧凑尺寸下自动转为可调整大小的 Sheet,并支持程序化控制列宽和展示状态。

核心内容

Inspector 是什么

你在 Keynote 里一定见过右侧面板:选中一个形状,右侧弹出格式设置。在 Shortcuts 里,右侧显示的是可用动作库。这些就是 Inspector。

00:31

以前开发者要自己用 NavigationSplitView 或手动管理 Sheet 来实现类似效果。代码复杂不说,还要自己处理不同尺寸下的适配。iOS 17 开始,SwiftUI 提供了原生的 inspector 修饰符,一行代码搞定。

01:51

放置位置决定外观

Inspector 的行为取决于它在视图层级中的位置。放在 NavigationStack 内部时,它显示在导航栏下方;放在外部时,它占据整列高度。

06:01

Toolbar 的位置也有讲究。在 Inspector 的 view builder 里声明的 toolbar 会显示在 Inspector 上方的工具栏区域;在外部声明的则显示在主内容的工具栏里。当 Inspector 以 Sheet 形式呈现时,内部声明的 toolbar 会跟着进入 Sheet。

06:44

Sheet 展示自定义

iOS 16.4 引入的 Sheet 自定义修饰符可以和 Inspector 组合使用。你可以设置背景材质、定义多个高度档位、控制背景内容的交互。

09:37

比如你想让 Inspector 在紧凑模式下以 Sheet 呈现时,小高度不遮挡背景内容,但展开到中等高度后显示遮罩。这只需要两个修饰符就能实现。

11:58

详细内容

基础用法

struct ContentView: View {
    @State private var state = AppState()
    @State private var presented = true

    var body: some View {
        AnimalTable(state: $state)
            .inspector(isPresented: $presented) {
                AnimalInspectorForm(animal: $state.binding())
                    .inspectorColumnWidth(
                        min: 200, ideal: 300, max: 400)
                    .toolbar {
                        Spacer()
                        Button {
                            presented.toggle()
                        } label: {
                            Label("Toggle Inspector", systemImage: "info.circle")
                        }
                    }
            }
    }
}

03:54

关键点:

  • .inspector(isPresented:) 接收一个 Binding<Bool> 控制展示状态
  • Inspector 的内容在 trailing closure 中定义
  • .inspectorColumnWidth(min:ideal:max:) 设置列宽范围,ideal 是首次启动时的宽度,用户调整后会持久化
  • Inspector 内部声明的 .toolbar 会显示在 Inspector 上方的工具栏区域
  • Inspector 默认使用 grouped form 样式,不需要手动设置

在 NavigationStack 内部使用

struct Example1: View {
    @State private var state = AppState()

    var body: some View {
        NavigationStack {
            AnimalTable(state: $state)
                .inspector(isPresented: $state.inspectorPresented) {
                    AnimalInspectorForm(animal: $state.binding())
                }
                .toolbar {
                    Button {
                        state.inspectorPresented.toggle()
                    } label: {
                        Label("Toggle Inspector", systemImage: "info.circle")
                    }
                }
        }
    }
}

07:14

关键点:

  • Inspector 放在 NavigationStack 内部时,显示在导航栏下方(under toolbar 样式)
  • Toolbar 在主内容上声明,显示在导航栈的工具栏里
  • 在紧凑尺寸下,Inspector 以 Sheet 呈现,toolbar 按钮仍留在主内容工具栏

在 NavigationStack 外部使用

struct Example2: View {
    @State private var state = AppState()

    var body: some View {
        NavigationStack {
            AnimalTable(state: $state)
        }
        .inspector(isPresented: $state.inspectorPresented) {
            AnimalInspectorForm(animal: $state.binding())
                .toolbar {
                    ToolbarItem(placement: .principal) {
                        HStack {
                            Button {
                            } label: {
                                Image(systemName: "rectangle.and.pencil.and.ellipsis")
                            }
                            Button {
                            } label: {
                                Image(systemName: "pawprint.circle")
                            }
                        }
                    }
                }
        }
    }
}

07:55

关键点:

  • Inspector 放在 NavigationStack 外部时,占据整列高度(full height 样式)
  • Toolbar 在 Inspector 的 view builder 里声明,显示在 Inspector 上方的专用工具栏区域
  • 在紧凑尺寸下以 Sheet 呈现时,toolbar 内容会跟着进入 Sheet
// 方式一:放在 detail column 内部
NavigationSplitView {
    Sidebar()
} detail: {
    AnimalTable()
        .inspector(presented: $isPresented) {
            AnimalInspectorForm()
        }
}

// 方式二:放在 NavigationSplitView 外部
NavigationSplitView {
    Sidebar()
} detail: {
    AnimalTable()
}
.inspector(presented: $isPresented) {
    AnimalInspectorForm()
}

08:56

关键点:

  • NavigationSplitView 中,Inspector 可以放在 detail column 内部或整个 split view 外部
  • 两种放置方式都有效,选择取决于你希望 Inspector 与哪个层级的导航结构关联

结合 Sheet 展示自定义

.sheet(item: $nibbledFruit) { fruit in
    FruitNibbleBulletin(fruit: fruit)
        .presentationBackground(.thinMaterial)
        .presentationDetents([.height(200), .medium, .large])
        .presentationBackgroundInteraction(.enabled(upThrough: .height(200)))
}

09:49

关键点:

  • .presentationBackground(.thinMaterial) 设置 Sheet 背景为半透明材质,底层内容可以隐约透出
  • .presentationDetents([.height(200), .medium, .large]) 定义三个高度档位
  • .presentationBackgroundInteraction(.enabled(upThrough: .height(200))) 允许在高度为 200 时与背景内容交互,超过这个高度后显示遮罩

这些修饰符同样适用于 Inspector 在紧凑尺寸下的 Sheet 呈现:

.inspector(presented: $state.inspectorPresented) {
    AnimalInspectorForm(animal: $state.binding())
        .presentationDetents([.height(200), .medium, .large])
        .presentationBackgroundInteraction(.enabled(upThrough: .height(200)))
}

11:58

核心启发

1. 给任何选择型界面加上 Inspector

如果你的应用有表格、列表或网格,用户选中一行后需要查看或编辑详情,用 .inspector 替代手动管理的 Sheet 或 Split View。它自动适配 iPhone、iPad 和 Mac,不需要写平台判断代码。入口是在选中视图上添加 .inspector(isPresented:) 修饰符。

2. 用 Inspector 做辅助面板

像 Shortcuts 那样,Inspector 可以展示与主内容相关的工具库或属性面板。比如一个代码编辑器,主区域是代码,Inspector 显示文件大纲或属性检查器。入口是把工具内容放在 .inspector 的 trailing closure 中,用 inspectorColumnWidth 控制默认宽度。

3. 组合 Sheet 自定义实现分层交互

在 iPhone 上,Inspector 以 Sheet 呈现。你可以用 .presentationDetents.presentationBackgroundInteraction 实现分层交互:小高度时用户可以继续操作主内容,展开后才进入专注编辑模式。入口是给 Inspector 内容加上 presentation 修饰符。

4. 利用 toolbar 位置控制按钮归属

Inspector 相关的操作按钮(如关闭、更多选项)放在 Inspector 的 view builder 里声明,这样它们在 Sheet 模式下会出现在 Sheet 的工具栏中。主内容的操作按钮放在主视图上声明。入口是仔细规划 .toolbar 修饰符的位置。

关联 Session

评论

GitHub Issues · utterances