WWDC Quick Look 💓 By SwiftGGTeam
Build for iPad

Build for iPad

观看原视频

Highlight

iPadOS 14 的设计目标是为 iPad 带来更多的桌面级生产力体验,同时保留触屏操作的直观性。Session 介绍了 iPadOS 14 中四个核心的 UI 模式更新。

核心内容

iPad 屏幕足够大,但很多 iPad app 仍然沿用 iPhone 的单列导航。用户想在 Mail 里同时看邮箱和邮件,想在 Notes 里一边选择工具一边继续写画,想在 Home 里快速切换房间和设备;旧界面经常让他们在页面之间来回跳转,或者先关掉一个弹出层才能继续操作。

iPadOS 14 给 UIKit 补上了这条路径。UISplitViewController 新增按列组织界面的初始化方式,app 可以直接声明双列或三列布局,再为 primary、supplementary、secondary、compact 等列提供对应的 view controller。UIKit 会按 size class 展示合适的列,并自动提供显示、隐藏列的按钮和手势。

列里的列表也换成了现代做法。Apple 推荐用 UICollectionView 配合 UICollectionLayoutListConfiguration 创建 sidebar 外观,再用 UICollectionView.CellRegistrationUICollectionViewDiffableDataSource 把数据接到 cell 上。这样既能得到 iPadOS 14 的 sidebar 样式,也能继续使用 collection view 的拖拽排序、outline、swipe actions 和可定制 cell。

Session 后半段用 Shortcuts 做案例。Shortcuts 在 regular width 里把 tab bar 换成 collapsible sidebar,在 compact width 里仍然使用适合小屏的 tab bar 和列表。它没有维护两套完全割裂的 app,而是把两种导航流程放进同一个 UISplitViewController,再在 size class 切换时恢复用户正在看的 detail 状态。

详细内容

多列布局从 UISplitViewController 开始

01:57)iOS 14 的 UISplitViewController 新 initializer 接收一个 style,用来声明 app 需要双列还是三列。双列叫 primary 和 secondary,三列会在中间增加 supplementary。

let splitViewController = UISplitViewController(style: .doubleColumn)

关键点:

  • style: .doubleColumn 表示这个 split view 负责一个导航列和一个内容列。
  • 这个 initializer 会启用 session 中展示的新列管理 API。
  • Apple 建议把结构化成列的 app 直接以 UISplitViewController 作为 window 的 root view controller。

02:13)创建 split view 后,把具体 view controller 放进指定列。Home 的例子是 primary 放 sidebar,secondary 放首页内容。

splitViewController.setViewController(sidebarViewController, for: .primary)
splitViewController.setViewController(myHomeViewController, for: .secondary)

关键点:

  • .primary 通常承载导航入口,例如邮箱列表、房间列表或快捷指令分类。
  • .secondary 承载用户当前操作的主要内容。
  • UIKit 会根据 regular width 或 compact width 决定这些列怎样出现。

02:28)如果 app 的信息层级需要三列,可以把 style 换成 .tripleColumn,再为 supplementary 列提供中间层内容。Mail 的 mailbox、message list、message detail 就是这种模型。

let splitViewController = UISplitViewController(style: .tripleColumn)
splitViewController.setViewController(inboxViewController, for: .supplementary)

关键点:

  • .tripleColumn 适合导航、列表、详情同时存在的生产力 app。
  • .supplementary 是 primary 和 secondary 之间的中间列。
  • 这段 API 只负责结构,列里的具体交互仍由各自 view controller 管理。

compact width 可以使用不同导航流

04:02)regular width 能显示多列,compact width 没有足够水平空间。Session 展示了 Shortcuts 的做法:在 regular width 使用 sidebar,在 compact width 提供一个 tab bar controller。

splitViewController.setViewController(tabBarController, for: .compact)

关键点:

  • .compact 列用于 iPhone portrait、iPad Slide Over 等窄宽度环境。
  • compact flow 可以和 regular width 的 sidebar flow 不同。
  • 切换逻辑由 UISplitViewController 根据 trait collection 处理,app 需要负责保持用户所在位置。

05:29)多列界面不只是一种固定布局。preferredSplitBehavior 决定按钮和边缘手势在不同 display mode 之间怎样移动。

splitViewController.preferredSplitBehavior = .tile
splitViewController.preferredSplitBehavior = .displace
splitViewController.preferredSplitBehavior = .overlay

关键点:

  • .tile 偏向把列并排摆放,适合希望内容始终可见的 app。
  • .displace 会在三列显示时把 secondary 部分推开。
  • .overlay 让额外列覆盖在 secondary 上,适合短暂查看导航信息。

05:56)app 也可以主动请求隐藏或显示某一列。UIKit 会执行平滑过渡,并把自动生成的按钮放到各列 navigation bar 或 toolbar 中。

splitViewController.hideColumn(.primary)

splitViewController.showColumn(.supplementary)

关键点:

  • hideColumn 适合让用户专注当前内容,例如只看文档或邮件正文。
  • showColumn 可以把导航列或中间列表带回来。
  • session 还提到 presentsWithGestureshowsSecondaryOnlyButton,它们控制边缘手势和只显示 secondary 的按钮。

08:06)列只是容器,sidebar 里的列表要用 collection view。iOS 14 提供 UICollectionLayoutListConfiguration,可以直接指定 sidebar 外观。

let configuration = UICollectionLayoutListConfiguration(appearance: .sidebar)
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
let collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)

关键点:

  • .sidebar 会给 primary 列提供 iPadOS 14 的 sidebar 视觉样式。
  • UICollectionViewCompositionalLayout.list(using:) 生成垂直滚动的 list layout。
  • UICollectionView 继续保留 collection view 的灵活性,后续可以加 accessories、drag reordering、outline 和 swipe actions。

09:36)cell 不再需要为了简单列表就写 subclass。CellRegistration 把注册和配置放在一处,并通过 defaultContentConfiguration() 写入文字和图片。

let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, MyItem>
{ cell, indexPath, item in

    var content = cell.defaultContentConfiguration()

    content.text = item.title
    content.image = item.image

    cell.contentConfiguration = content
}

关键点:

  • UICollectionViewListCell 是 list 场景的标准 cell 类型。
  • MyItem 是 app 自己的数据类型,session 示例里包含 title 和 image。
  • defaultContentConfiguration() 会按当前 list/sidebar 环境返回合适的默认样式。
  • 设置 cell.contentConfiguration 后,cell 的文本、图片和内边距由系统配置统一管理。

10:31)最后用 diffable data source 把数据源接到 collection view。它负责根据 section 和 item 生成 cell。

let dataSource = UICollectionViewDiffableDataSource<Section, MyItem>
   (collectionView: collectionView)
{ collectionView, indexPath, item in
   return collectionView.dequeueConfiguredReusableCell(using: cellRegistration,
                                                       for: indexPath,
                                                       item: item)
}

关键点:

  • SectionMyItem 描述列表的数据结构。
  • closure 在 collection view 需要 cell 时被调用。
  • dequeueConfiguredReusableCell 同时完成复用和配置,避免手动管理 reuse identifier。
  • session 强调,多数 list 都会遵循 layout、cell registration、diffable data source 这个组合。

11:29)如果 supplementary 列展示的是内容列表,而不是导航入口,Human Interface Guidelines 建议用 sidebarPlain

let configuration = UICollectionLayoutListConfiguration(appearance: .sidebarPlain)
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
let collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)

关键点:

  • .sidebarPlain 适合文件夹项目、集合项目、邮件列表这类内容列表。
  • session 里提到它会呈现白色背景、分隔线和更偏内容的视觉处理。
  • primary column 常用 .sidebar,supplementary column 常见 .sidebarPlain

Shortcuts 案例把结构和状态串起来

15:35)Shortcuts 的 regular width 版本用 double column split view。左侧 sidebar 负责导航,用户选中项目后,右侧显示 detail。

let splitViewController = UISplitViewController(style: .doubleColumn)

// Primary column

let sidebar = SidebarViewController()
splitViewController.setViewController(sidebar, for: .primary)


// Secondary column

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    splitViewController.showDetailViewController(DetailViewController(), sender: self)
}

关键点:

  • SidebarViewController 放在 primary column。
  • sidebar 选择事件来自 UICollectionViewDelegatedidSelectItemAt
  • showDetailViewController 把新的 detail 放入 secondary column。
  • transcript 说明 Shortcuts 会在 size class 变化时重建当前 detail,避免用户从 split view 切到 compact width 后丢失当前位置。

20:39)Shortcuts 的 sidebar 也是 collection view。它用 section provider 读取当前 layout environment,在 compact width 下把 header 改成 section 的第一个 item,在 regular width 下不显示 header。

let layout = UICollectionViewCompositionalLayout(sectionProvider: sectionProvider,
         configuration: UICollectionViewCompositionalLayoutConfiguration())

func sectionProvider(_ section: Int, environment: NSCollectionLayoutEnvironment)
-> NSCollectionLayoutSection {
    var configuration = UICollectionLayoutListConfiguration(appearance: .sidebar)

    if (environment.traitCollection.horizontalSizeClass == .compact) {
        configuration.headerMode = .firstItemInSection
    } else {
        configuration.headerMode = .none
    }

    return NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: environment)
}

关键点:

  • sectionProvider 让每个 section 可以根据环境生成不同布局。
  • environment.traitCollection.horizontalSizeClass 是判断 compact/regular 的依据。
  • headerMode = .firstItemInSection 让 compact width 里仍能表达分组。
  • NSCollectionLayoutSection.list 返回真正的 list section。

减少 modality 是交互层面的目标

12:27)session 还强调,iPad app 应该减少会挡住任务流的 transient UI。Notes 的 color picker 会在用户开始绘制时自动消失,Contacts 的菜单允许用户点按菜单外侧后继续用同一次触摸滚动。这里没有固定 API 配方,Apple 给出的判断标准是观察用户是否已经开始下一步操作,例如滚动、绘制或响应外部事件。

关键点:

  • 这是 UIKit 在 iOS 14 中做出的系统级交互改进。
  • app 自己也应该观察滚动、绘制、 incoming events 等信号。
  • 目标是让用户继续当前任务,而不是先处理关闭弹窗这一步。

核心启发

  • 给资料型 app 增加三列阅读模式 做什么:把「分类、列表、详情」同时放在 iPad 横屏界面里。 为什么值得做:10105 展示了 .tripleColumn 和 supplementary column,适合邮件、笔记、知识库、文件管理这类信息层级明确的 app。 怎么开始:用 UISplitViewController(style: .tripleColumn) 建根容器,把分类放 .primary,项目列表放 .supplementary,详情放 .secondary

  • 把旧 tab bar 导航迁移成 collapsible sidebar 做什么:在 regular width 中把底部 tab bar 换成可折叠 sidebar,同时为 compact width 保留 tab bar。 为什么值得做:Shortcuts 通过 sidebar 提供更多导航入口,又用 .compact 列保留小屏流程。 怎么开始:为 split view 设置 primary sidebar view controller,再用 setViewController(tabBarController, for: .compact) 提供窄宽度版本。

  • 用 collection view 统一 sidebar、内容列表和 outline 做什么:把 iPad app 里的 table view 列表逐步替换成 collection view list。 为什么值得做:session 明确推荐 UICollectionView 作为现代 list 方案,它能继续扩展 accessories、拖拽排序、outline 和 swipe actions。 怎么开始:先用 UICollectionLayoutListConfiguration(appearance: .sidebar) 搭 primary sidebar,再接 CellRegistrationUICollectionViewDiffableDataSource

  • 为内容列选择 sidebarPlain 外观 做什么:区分导航列表和内容列表,让 supplementary column 更像文件、邮件或项目清单。 为什么值得做:Human Interface Guidelines 在 session 中给出的规则是:内容集合或文件夹里的项目使用 sidebar plain。 怎么开始:在 supplementary 列创建 collection view 时使用 UICollectionLayoutListConfiguration(appearance: .sidebarPlain)

  • 主动收起挡住任务流的临时界面 做什么:当用户继续绘制、滚动或处理输入时,自动关闭已经完成使命的 popover、menu 或 transient control。 为什么值得做:session 用 Notes 和 Contacts 说明,多余的 dismiss tap 会打断 iPad 上的连续操作。 怎么开始:从 app 中最常见的 popover 或 menu 开始,找到用户下一步动作信号,在动作发生时关闭临时 UI 并让原动作继续。

关联 Session

评论

GitHub Issues · utterances