Build for iPad
Swift & UI 入门 26m

为 iPad 构建应用

Build for iPad

2020年6月23日

在 Apple 官方观看视频

一句话判断

iPadOS 14 为 iPad 带来了更桌面化的体验:侧边栏导航、下拉菜单、工具栏、以及更紧凑的模态视图。如果你的 iPad App 还停留在”放大版 iPhone”的阶段,这个 Session 给你提供了完整的升级路线图。

这场 Session 讲了什么

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

第一,全新的侧边栏模式(UISplitViewController 的三栏布局),支持可折叠的分组、拖拽排序和上下文菜单。第二,下拉菜单(UIMenu)替代了传统的 Action Sheet,让用户可以通过点击或触控板触发层级式菜单。第三,紧凑的日期选择器和编辑弹出框,不再占据整个屏幕。第四,工具栏(Toolbar)可以在侧边栏切换时动态调整布局。Session 通过 Apple 自家的 Files、Notes 等 App 来演示这些模式的实际应用。

值得深挖的点

侧边栏的三栏布局

iPadOS 14 推荐使用 UISplitViewController 的三栏布局(Supplementary Column)。主视图是可折叠的侧边栏,中间是列表,右边是详情。用户可以通过按钮或手势来展开/折叠侧边栏。这种布局在 Files、Notes、Music 等 Apple 自家 App 中已经全面采用。对于有导航层级结构的 App 来说,这是最推荐的布局模式。

从 Action Sheet 到 UIMenu

iPadOS 14 引入了更紧凑的上下文交互方式。传统的 Action Sheet 在 iPad 上占据太多屏幕空间。新的 UIMenu 可以作为一个下拉菜单从按钮或工具栏项触发,支持层级结构和图标。用户还可以通过触控板右键或长按来触发上下文菜单。

代码片段

配置三栏 Split View 布局

场景:为邮件类 App 创建侧边栏 + 列表 + 详情的三栏布局。

import UIKit

class MailSplitViewController: UISplitViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 使用三栏布局
        preferredDisplayMode = .twoBesideSecondary
        preferredSplitBehavior = .displace  // 侧边栏展开时推开内容
        
        // 设置侧边栏的显示模式
        preferredDisplayMode = .primarySidebar  // 默认显示侧边栏
        
        // 创建三个视图控制器
        let sidebarVC = MailboxSidebarViewController()
        let mailListVC = MailListViewController()
        let mailDetailVC = MailDetailViewController()
        
        // 使用 NavigationController 包装
        let sidebarNav = UINavigationController(rootViewController: sidebarVC)
        let listNav = UINavigationController(rootViewController: mailListVC)
        let detailNav = UINavigationController(rootViewController: mailDetailVC)
        
        // 设置为三栏
        setViewController([sidebarNav, listNav, detailNav])
        
        // 侧边栏的显示/隐藏
        toggleSidebarMode()
    }
    
    private func toggleSidebarMode() {
        // 在侧边栏按钮点击时切换显示模式
        let targetMode: UISplitViewController.DisplayMode = 
            displayMode == .primaryHidden ? .primaryBesideSecondary : .primaryHidden
        
        preferredDisplayMode = targetMode
    }
}

使用 UIMenu 替代 Action Sheet

场景:为工具栏按钮创建下拉菜单。

class MailToolbarViewController: UIViewController {
    
    @IBAction func moreButtonTapped(_ sender: UIBarButtonItem) {
        // 创建层级菜单
        let moveMenu = UIMenu(title: "移动", image: UIImage(systemName: "folder"), options: [], children: [
            UIAction(title: "收件箱", image: UIImage(systemName: "tray")) { _ in
                self.moveToInbox()
            },
            UIAction(title: "已归档", image: UIImage(systemName: "archivebox")) { _ in
                self.moveToArchive()
            },
            UIAction(title: "垃圾邮件", image: UIImage(systemName: "trash"), attributes: .destructive) { _ in
                self.moveToSpam()
            }
        ])
        
        let mainMenu = UIMenu(title: "", children: [
            UIAction(title: "回复", image: UIImage(systemName: "arrowshape.turn.up.left")) { _ in
                self.reply()
            },
            UIAction(title: "转发", image: UIImage(systemName: "arrowshape.turn.up.right")) { _ in
                self.forward()
            },
            moveMenu,  // 嵌套子菜单
            UIMenu.separator(),  // 分隔线
            UIAction(title: "标记未读", image: UIImage(systemName: "circle")) { _ in
                self.markUnread()
            }
        ])
        
        // 作为下拉菜单显示
        sender.menu = mainMenu
    }
}

配置侧边栏的列表样式

场景:使用 UICollectionView 创建 macOS 风格的侧边栏。

import UIKit

class MailboxSidebarViewController: UIViewController {
    
    private var collectionView: UICollectionView!
    private var dataSource: UICollectionViewDiffableDataSource<String, MailboxItem>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupSidebar()
    }
    
    private func setupSidebar() {
        // 使用 Sidebar 列表外观
        var layoutConfig = UICollectionLayoutListConfiguration(appearance: .sidebar)
        
        // 配置滑动操作
        layoutConfig.leadingSwipeActionsConfigurationProvider = { indexPath in
            let action = UIContextualAction(style: .normal, title: "置顶") { _, _, completion in
                completion(true)
            }
            return UISwipeActionsConfiguration(actions: [action])
        }
        
        let layout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        
        // 注册 Cell
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, MailboxItem> {
            cell, indexPath, item in
            
            var content = cell.defaultContentConfiguration()
            content.text = item.name
            content.image = UIImage(systemName: item.iconName)
            content.secondaryText = "\(item.unreadCount)"
            cell.contentConfiguration = content
        }
        
        dataSource = UICollectionViewDiffableDataSource<String, MailboxItem>(
            collectionView: collectionView
        ) { collectionView, indexPath, item in
            collectionView.dequeueConfiguredReusableCell(
                using: cellRegistration, for: indexPath, item: item
            )
        }
    }
}

最佳实践

已有项目:如果你的 iPad App 使用了 UISplitViewController,升级到三栏布局。把侧边栏的 UICollectionView 列表外观切换为 .sidebar 模式。将 Action Sheet 替换为 UIMenu,特别是在工具栏和导航栏的场景下。

新项目:从产品规划阶段就采用三栏布局。这是 iPadOS 的标准导航模式,Apple 自家 App 已经全面采用。使用 UICollectionLayoutListConfiguration(appearance: .sidebar) 来创建侧边栏,它已经内置了合适的间距、字体和交互反馈。

还有什么值得关注

  • iPadOS 14 的 UISplitViewController 支持自动记忆用户选择的侧边栏展开/折叠状态。
  • 侧边栏外观(.sidebar)自动使用半透明材质背景,不需要手动配置。
  • 搭配 Session 10093 和 10094 可以了解触控板交互的适配细节。
WWDC 2020