SwiftUI on iPad: Add toolbars, titles, and more
Swift & UI 进阶 20m

SwiftUI on iPad:添加工具栏、标题等

SwiftUI on iPad: Add toolbars, titles, and more

2022年6月6日

在 Apple 官方观看视频

一句话判断

这可能是今年最实用的 SwiftUI Session 之一——苹果终于把 iPad 上的工具栏、导航标题、overflow 菜单这些细节都补齐了,让你的 iPad App 不再像个放大版的 iPhone App。

这场 Session 讲了什么

SwiftUI 在 iPad 上的工具栏支持一直是短板。iOS 16 / iPadOS 16 补上了大量缺失的 API,让开发者能精细控制 iPad 屏幕上各种 UI 元素的布局。Session 重点讲了以下几个方面:

工具栏的 overflow 菜单。 当工具栏按钮太多放不下时,iPad 上会自动出现一个 overflow 菜单(三个点的按钮),多余的选项会折叠进去。开发者现在可以用 ToolbarItem 的 placement 参数来精确控制每个按钮的位置。

toolbarRole(.editor) 和次要操作。 新的 toolbarRole 修饰符让你指定工具栏的角色类型。.editor 角色会改变工具栏的布局方式,把次要操作放到合适的位置。这是一个语义化的 API,告诉系统”这个工具栏是用来编辑内容的”,系统会据此做出合适的布局决策。

导航标题菜单。 现在可以给导航标题加上一个菜单,用户点击标题时弹出选项列表。这在 iPad 上特别有用——标题栏有足够的点击区域。

ControlGroup。 新的 ControlGroup 容器可以把多个按钮组合在一起,在视觉上形成一个整体。适合把相关操作归到一组。

navigationDocument。 新的修饰符让你在标题栏显示当前打开的文档名称,系统会自动处理标题的展示方式。

值得深挖的点

语义化的工具栏 API 是正确方向。 苹果没有让你手动计算按钮的布局位置,而是通过 placement 和 role 这样的语义化参数来描述意图。系统会根据设备类型、屏幕方向、可用空间来自动决定最佳的布局。这种”描述意图而非描述实现”的设计哲学贯穿了 SwiftUI 的整个工具栏体系。

工具栏自定义是用户期待已久的功能。 iPad 用户一直期望能像 macOS 一样自定义工具栏按钮的排列。iOS 16 的底层 API 已经为这个能力做好了准备,虽然目前 SwiftUI 层面还没有直接暴露自定义拖拽排序的 API,但 ToolbarItem 的架构设计明显预留了这个扩展空间。

代码片段

struct DocumentEditorView: View {
    let document: Document
    
    var body: some View {
        NavigationStack {
            DocumentContent(document: document)
                .navigationTitle(document.name)
                .navigationDocument(document.url)
                // 编辑器角色的工具栏
                .toolbar {
                    ToolbarItemGroup(placement: .primaryAction) {
                        Button(action: formatText) {
                            Label("Format", systemImage: "textformat")
                        }
                        Button(action: insertImage) {
                            Label("Insert", systemImage: "photo")
                        }
                    }
                    // 次要操作会自动进入 overflow 菜单
                    ToolbarItemGroup(placement: .secondaryAction) {
                        Button(action: shareDocument) {
                            Label("Share", systemImage: "square.and.arrow.up")
                        }
                        Button(action: printDocument) {
                            Label("Print", systemImage: "printer")
                        }
                    }
                }
                .toolbarRole(.editor)
                // 导航标题菜单
                .navigationBarTitleMenu {
                    Button("Rename") { renameDocument() }
                    Button("Duplicate") { duplicateDocument() }
                    Button("Move...") { moveDocument() }
                }
        }
    }
}

// ControlGroup 示例
.toolbar {
    ToolbarItemGroup(placement: .primaryAction) {
        ControlGroup {
            Button(action: undo) {
                Label("Undo", systemImage: "arrow.uturn.backward")
            }
            Button(action: redo) {
                Label("Redo", systemImage: "arrow.uturn.forward")
            }
        }
    }
}

最佳实践

  • 使用语义化的 placement 参数(如 .primaryAction, .secondaryAction, .navigation)而非硬编码位置,这样系统在不同设备和方向下能做出正确的布局决策。
  • 次要操作用 .secondaryAction,系统会自动把它们放到 overflow 菜单里,而不是试图在工具栏上挤下所有按钮。
  • toolbarRole(.editor) 要用在确实有编辑操作的场景,它会影响工具栏的整体视觉风格。
  • iPad 上要测试横屏和竖屏两种方向的工具栏布局,因为可用空间差异很大。

还有什么值得关注

  • navigationDocument 会自动在标题旁显示一个文档图标,用户点击可以查看文件信息。
  • ControlGroup 在 visionOS(当时还没公布,但 API 设计上已经为未来设备做好了准备)上可能有不同的视觉呈现。
  • 工具栏按钮的 Label 同时提供了文字和图标,系统会根据可用空间决定显示哪一个。
  • 如果你从 UIKit 迁移,SwiftUI 的工具栏 API 对应的是 UIToolbar 和 UINavigationItem 的组合。
WWDC 2022