WWDC Quick Look 💓 By SwiftGGTeam
SwiftUI on iPad: Add toolbars, titles, and more

SwiftUI on iPad: Add toolbars, titles, and more

观看原视频

Highlight

iOS 16 和 iPadOS 16 为 SwiftUI 工具栏带来了 Mac 级别的能力:overflow 菜单自动折叠、secondary action 放置、用户可自定义工具栏、ControlGroup 分组、可编辑导航标题和文档关联,让 iPad 应用的工具栏终于不再只是 iPhone 的放大版。

核心内容

从手动 Menu 到自动 Overflow

02:05

很多开发者在 iPhone 上习惯用一个 “More” 菜单来收纳工具栏按钮。在 iPad 的大屏幕上,这种写法浪费了可用空间。

iOS 16 的解决方案是 overflow 菜单:系统会自动把放不下的按钮收进菜单,不需要你手动写 Menu。

ToolbarItem 改成 ToolbarItemGroup,去掉手动包裹的 Menu,把按钮直接作为 group 的内容:

PlaceDetailContent(place: $place)
    .toolbar {
        ToolbarItemGroup(placement: .primaryAction) {
            FavoriteToggle(place: $place)
            AdjustImageButton(place: $place)
            AdjustMapButton(place: $place)
        }
    }

关键点

  • ToolbarItemGroup 会为每个子视图创建独立的工具栏项
  • 在 iPad 和 Mac 上,系统会自动把放不下的项移入 overflow 菜单
  • 不需要手动维护 Menu 的展开逻辑

Secondary Action 和 ToolbarRole

04:05

导航栏有三个区域:leading(左侧)、center(中间)、trailing(右侧)。leading 和 trailing 放控件,center 放导航标题。

iOS 16 新增了 .secondaryAction placement。这类操作使用频率不高,但值得单独展示。默认情况下它们不会直接显示在工具栏上,而是进入 overflow 菜单。

toolbarRole(.editor) 改变布局行为:

PlaceDetailContent(place: $place)
    .toolbar {
        ToolbarItemGroup(placement: .secondaryAction) {
            FavoriteToggle(place: $place)
            AdjustImageButton(place: $place)
            AdjustMapButton(place: $place)
        }
    }
    .toolbarRole(.editor)

关键点

  • .editor 角色告诉导航栏”这个页面用于编辑内容”
  • 导航栏会把标题从 center 移到 leading,腾出 center 区域给 secondary action
  • 在 compact size class(如 iPhone 竖屏)上,标题仍保持在 center

用户可自定义工具栏

05:37

macOS 长期支持工具栏自定义,iPadOS 16 也引入了这项能力。

启用自定义需要三个条件:

PlaceDetailContent(place: $place)
    .toolbar(id: "place") {
        ToolbarItem(id: "favorite", placement: .secondaryAction) {
            FavoriteToggle(place: $place)
        }
        ToolbarItem(id: "image", placement: .secondaryAction) {
            AdjustImageButton(place: $place)
        }
        ToolbarItem(id: "map", placement: .secondaryAction) {
            AdjustMapButton(place: $place)
        }
    }
    .toolbarRole(.editor)

关键点

  • 每个 ToolbarItem 必须有唯一的 id,且跨启动保持一致
  • toolbar modifier 本身也需要一个 id
  • 只有 ToolbarItem 支持自定义,ToolbarItemGroup 不支持
  • iPad 上只有 secondary action 支持自定义,primary action 不支持

默认隐藏的按钮

06:59

有些操作对特定用户群体有用,但不应该默认显示。用 showsByDefault: false 让这些按钮初始时待在自定义面板里:

ToolbarItem(id: "share", placement: .secondaryAction, showsByDefault: false) {
    ShareLink(item: place)
}

用户可以从自定义面板拖拽这类按钮到工具栏上。

ControlGroup 分组

07:45

相关的按钮可以用 ControlGroup 组合在一起,作为一个整体添加或移除:

ToolbarItem(id: "image", placement: .secondaryAction) {
    ControlGroup {
        AdjustImageButton(place: $place)
        AdjustMapButton(place: $place)
    } label: {
        Label("Edits", systemImage: "wand.and.stars")
    }
}

关键点

  • 提供 label 后,当空间不足时整个组会折叠成一个菜单
  • 没有 label 时,按钮在空间不足时会逐个进入 overflow 菜单
  • 用户可以把整个 ControlGroup 作为一个单元添加或移除

详细内容

完整的工具栏配置示例

PlaceDetailContent(place: $place)
    .toolbar(id: "place") {
        ToolbarItem(id: "new", placement: .primaryAction) {
            NewButton()
        }
        ToolbarItem(id: "favorite", placement: .secondaryAction) {
            FavoriteToggle(place: $place)
        }
        ToolbarItem(id: "image", placement: .secondaryAction) {
            ControlGroup {
                AdjustImageButton(place: $place)
                AdjustMapButton(place: $place)
            } label: {
                Label("Edits", systemImage: "wand.and.stars")
            }
        }
        ToolbarItem(id: "share", placement: .secondaryAction, showsByDefault: false) {
            ShareLink(item: place)
        }
    }
    .toolbarRole(.editor)

关键点

  • id: "new"primaryAction,固定在 trailing 区域,不可自定义
  • id: "favorite"id: "image"secondaryAction,默认显示在 center 区域,可自定义
  • id: "share"showsByDefault: false,初始隐藏在自定义面板中
  • 所有 id 必须唯一且跨启动保持一致,因为 SwiftUI 用 id 持久化用户的自定义配置

导航标题菜单

10:44

导航标题现在支持菜单,类似于 macOS 的 File 菜单:

PlaceDetailContent(place: $place)
    .navigationTitle(place.name) {
        MyPrintButton()
    }

标题右侧会出现菜单指示器,点击后弹出包含你提供的操作的菜单。

可编辑的导航标题

11:07

传入 Binding 告诉系统这个标题支持编辑:

PlaceDetailContent(place: $place)
    .navigationTitle($place.name) {
        MyPrintButton()
        RenameButton()
    }

关键点

  • $place.name 传入 Binding,系统知道标题可编辑
  • RenameButton() 触发重命名流程
  • 点击 RenameButton 后,标题变成可编辑的文本框

关联文档

11:37

把文档关联到视图,标题菜单会显示文档预览头:

PlaceDetailContent(place: $place)
    .navigationTitle($place.name) {
        MyPrintButton()
        RenameButton()
    }
    .navigationDocument(place.url)

关键点

  • 标题菜单会显示文档预览,支持拖拽
  • 提供 URL 时,macOS 上还会自动配置窗口工具栏的代理图标
  • 参数可以是符合 Transferable 的类型,也可以直接传 URL

核心启发

1. 给笔记 App 加可自定义工具栏

  • 做什么:让用户决定工具栏上显示哪些格式化按钮
  • 为什么值得做:不同用户的使用习惯差异很大,有人常用粗体,有人常用代码块,自定义工具栏让每个人都能有自己的工作流
  • 怎么开始:用 toolbar(id:) + ToolbarItem(id:placement:) 给每个按钮分配唯一 id,把不常用的按钮设 showsByDefault: false

2. 给图片编辑 App 加 ControlGroup 工具栏

  • 做什么:把旋转、裁剪、滤镜等操作按功能分组
  • 为什么值得做:相关操作视觉上归为一组,用户更容易找到,空间不足时整组折叠成菜单
  • 怎么开始:用 ControlGroup 包裹相关按钮,给 group 加 label 让它在空间不足时折叠成菜单

3. 给文件管理 App 加文档标题菜单

  • 做什么:点击导航标题弹出文件操作菜单(重命名、移动、分享)
  • 为什么值得做:标题是用户视线自然停留的位置,把常用文件操作放在这里比藏在工具栏更直观
  • 怎么开始:用 .navigationTitle($document.name) { ... } 给标题加菜单,传入 Binding 支持重命名

4. 给 iPad 阅读 App 优化工具栏布局

  • 做什么:用 toolbarRole(.editor) 让 secondary action 占据 center 区域
  • 为什么值得做:iPad 屏幕宽度足够,把书签、目录、字体设置等按钮放在 center 比全塞进 overflow 菜单更易于访问
  • 怎么开始:把阅读相关的操作标记为 .secondaryAction,加上 .toolbarRole(.editor),系统会自动把标题移到左侧腾出空间

关联 Session

评论

GitHub Issues · utterances