WWDC Quick Look 💓 By SwiftGGTeam
SwiftUI on the Mac: Build the fundamentals

SwiftUI on the Mac: Build the fundamentals

观看原视频

Highlight

优秀的 Mac App 有四个共同特征:灵活(可定制)、熟悉(遵循系统约定)、广阔(利用大屏幕)、精确(高信息密度)。Session 用一个园艺管理 App 展示了如何用 SwiftUI 实现这些原则。

核心内容

SwiftUI 在 macOS Monterey 中的改进让它真正可以用来构建生产级 Mac App。Session 用一个园艺追踪 App 作为贯穿示例,从零开始构建。

四个核心原则的 SwiftUI 实现:

  • 灵活:用 @SceneStorage 保存侧边栏展开状态和选中项,让用户的界面配置在重启后保持
  • 熟悉:用 Commands 修饰符构建标准菜单栏,ToolbarItem 放在约定位置
  • 广阔:用 Table 展示多列数据,DisclosureGroup 控制内容显示
  • 精确:Table 的紧凑行高、Sidebar 的最小宽度设置

详细内容

Sidebar:大纲视图的构建

Mac App 的 Sidebar 通常使用 DisclosureGroup 实现大纲视图。配合 @SceneStorage 可以保存展开状态(06:09)。

@SceneStorage("expansionState") private var expansionState = Set<String>()

var body: some View {
    List {
        DisclosureGroup("Current Year", isExpanded: $isCurrentExpanded) {
            ForEach(currentYearGardens) { garden in
                Label(garden.name, systemImage: "leaf")
                    .tag(garden.id)
            }
        }
        DisclosureGroup("History") {
            GardenHistoryOutline()
        }
    }
    .frame(minWidth: 150)
}

关键点:

  • @SceneStorage 自动在 App 退出时保存状态,重启后恢复
  • DisclosureGroup 提供可折叠的大纲视图
  • tag() 配合 selection binding 支持键盘导航和选中状态

Table:多列数据展示

Table 是 macOS Monterey 新增的组件,类似 NSTableView(09:14)。

Table(plants, selection: $selectedPlant, sortOrder: $sortOrder) {
    TableColumn("Name", value: \.name)
    TableColumn("Variety", value: \.variety)
    TableColumn("Days to Maturity") { plant in
        Text("\(plant.daysToMaturity)")
    }
}

关键点:

  • Table 支持多列显示,比 List 适合更复杂的数据
  • selection binding 支持单选或多选
  • sortOrder binding 自动支持列排序
  • 在 iPadOS 上自动降级为 List

Toolbar:工具栏的配置

工具栏是 Mac 用户发现功能的标准位置(11:21)。

Table(plants) {
    // 列定义...
}
.toolbar {
    ToolbarItem(placement: .primaryAction) {
        Button(action: addPlant) {
            Label("Add Plant", systemImage: "plus")
        }
    }
    ToolbarItem(placement: .automatic) {
        DisplayModePicker()
    }
}

关键点:

  • ToolbarItemplacement 决定在工具栏中的位置
  • .primaryAction 放在左侧显眼位置
  • .automatic 让系统根据空间决定位置
  • 可以根据选中状态禁用按钮

Searchable:搜索功能

Mac 上始终有硬件键盘,搜索是快速筛选数据的有效方式(12:15)。

Table(plants.filter { searchText.isEmpty || $0.name.contains(searchText) }) {
    // 列定义...
}
.searchable(text: $searchText)

关键点:

  • .searchable 修饰符添加搜索框到工具栏
  • Mac 上搜索框固定在工具栏右侧
  • 支持 Cmd+F 快捷键聚焦搜索框
  • 自动处理焦点和键盘导航

Commands:菜单栏配置

菜单栏是 Mac 用户探索功能的重要途径(13:17)。

@main
struct GardenApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            SidebarCommands()
            CommandGroup(before: .newItem) {
                Button("Add Plant") {
                    // 添加植物
                }
                .keyboardShortcut("n", modifiers: .command)
            }
            CommandMenu("Plants") {
                Button("Mark as Watered") {
                    // 标记为已浇水
                }
                .keyboardShortcut("w", modifiers: [.command, .shift])
            }
        }
    }
}

关键点:

  • .commands 修饰符配置菜单栏
  • SidebarCommands() 添加标准的侧边栏切换菜单
  • CommandGroup(before:) 在系统菜单前插入自定义命令
  • CommandMenu 创建新的顶级菜单

FocusedSceneValue:窗口间通信

多窗口 App 的菜单命令需要作用于前台窗口(14:01)。

struct GardenDetail: View {
    @Binding var garden: Garden?

    var body: some View {
        Table(plants, selection: $selectedPlants) {
            // 列定义...
        }
        .focusedSceneValue(\.selectedGarden, $garden)
        .focusedSceneValue(\.selectedPlants, $selectedPlants)
    }
}
struct PlantCommands: Commands {
    @FocusedValue(\.selectedGarden) private var focusedGarden: Binding<Garden>?

    var body: some Commands {
        CommandMenu("Plants") {
            Button("Mark as Watered") {
                focusedGarden?.wrappedValue.markAsWatered()
            }
            .disabled(focusedGarden == nil)
        }
    }
}

关键点:

  • @FocusedValue 读取当前焦点窗口的值
  • focusedSceneValue 暴露 binding 给菜单命令
  • 菜单命令自动作用于前台窗口
  • 不需要手动追踪窗口焦点

核心启发

  1. 用 @SceneStorage 保存用户界面状态:Mac App 有多个窗口,每个窗口的状态应该独立保存。@SceneStorage 自动处理这一点,无需手动读写 UserDefaults。

  2. 用 Table 替代 List 展示复杂数据:需要多列、可排序的数据时,用 Table 而不是 ListTable 专为信息密度设计,支持排序和选择,在 iPadOS 上自动降级为 List

  3. 为所有操作提供菜单栏快捷键:Mac 用户期望通过菜单栏和快捷键访问所有功能。即使 App 有工具栏,也应该在菜单栏中重复这些操作,增加功能的可发现性。

  4. 用 Searchable 添加搜索功能:Mac 的工具栏空间充足,.searchable 让搜索框出现在标准位置,支持 Cmd+F 聚焦,符合用户习惯。

  5. 利用 FocusedSceneValue 实现窗口间协作:多窗口 App 的菜单命令需要作用于前台窗口。focusedSceneValue + @FocusedValue 实现了这一点,无需手动管理焦点追踪。

关联 Session

评论

GitHub Issues · utterances