Designed for iPad
Design 入门 22m

为 iPad 而设计

Designed for iPad

2020年6月23日

在 Apple 官方观看视频

一句话判断

iPad 不是”大号 iPhone”——如果你的 iPad 应用只是 iPhone 版的等比放大,你浪费了这个平台最有价值的特性:多窗口、触控笔、键盘和宽屏布局。

这场 Session 讲了什么

这场 Session 从设计角度重新审视了 iPad 应用的体验设计。iPadOS 14 在多窗口管理、Scribble(Apple Pencil 手写输入)、Sidebar 导航和 Compact Layout 方面做了大量改进。Session 强调了”iPad 独特体验”的核心:大屏幕不是用来看更多内容的,而是用来做更复杂的事情的。

Sidebar 导航是 iPadOS 14 最重要的 UI 变化之一。Apple 推荐所有 iPad 应用采用”Sidebar + Content”的两栏布局,替代传统的底部 Tab Bar。Sidebar 可以折叠为 compact 模式(只显示图标),也可以展开显示完整的标签和分组。这个设计充分利用了 iPad 的宽屏空间——导航在左侧,内容在右侧,一眼就能看到完整的信息层级。

Scribble 是 iPadOS 14 的新特性,用户可以在任何文本框中直接用 Apple Pencil 手写,系统实时将笔迹转换为文本。这对开发者来说几乎是透明的——你的应用不需要做任何适配就能支持 Scribble。但 Session 建议在自定义文本控件中测试 Scribble 的兼容性,特别是那些有自定义输入逻辑的控件。

Session 还讨论了键盘快捷键的最佳实践。iPad 的键盘用户(外接键盘或 Magic Keyboard)期望用 Cmd+N 新建、Cmd+W 关闭窗口、Cmd+, 打开设置。你的应用应该支持这些标准快捷键。

值得深挖的点

Sidebar 的三种显示模式

iPadOS 的 Sidebar 有三种模式:expanded(完整显示图标和文字)、compact(只显示图标)和 hidden(完全隐藏,通过按钮切换)。你的布局需要自适应这三种状态——expanded 时内容区域变窄,compact 时内容区域变宽。SwiftUI 的 NavigationSplitView 自动处理了这个适配,但用 UIKit 实现需要手动监听 sidebar 的状态变化。

多窗口的设计考量

iPadOS 支持同一应用的多个窗口。比如邮件应用可以同时打开两封邮件并排对比。Session 讨论了多窗口场景下的状态管理:每个窗口应该有独立的导航栈,但共享数据源。用户在窗口 A 中删除了一封邮件,窗口 B 应该立即反映这个变化。

代码片段

import SwiftUI

// iPad Sidebar + Content 布局
struct iPadAppView: View {
    @State private var selectedItem: NavigationItem?
    @State private var columnVisibility: NavigationSplitViewVisibility = .all
    
    var body: some View {
        NavigationSplitView(columnVisibility: $columnVisibility) {
            // Sidebar(左侧导航)
            List(NavigationItem.allItems, selection: $selectedItem) { item in
                NavigationLink(value: item) {
                    Label(item.title, systemImage: item.icon)
                }
            }
            .navigationTitle("我的应用")
            #if os(iOS)
            .navigationBarTitleDisplayMode(.inline)
            #endif
        } detail: {
            // Content(右侧内容区)
            if let item = selectedItem {
                DetailView(item: item)
            } else {
                Text("请选择一个项目")
                    .foregroundColor(.secondary)
            }
        }
    }
}

struct NavigationItem: Identifiable, Hashable {
    let id: String
    let title: String
    let icon: String
    
    static let allItems: [NavigationItem] = [
        NavigationItem(id: "inbox", title: "收件箱", icon: "tray"),
        NavigationItem(id: "sent", title: "已发送", icon: "paperplane"),
        NavigationItem(id: "drafts", title: "草稿", icon: "doc"),
        NavigationItem(id: "archive", title: "归档", icon: "archivebox"),
        NavigationItem(id: "trash", title: "已删除", icon: "trash"),
    ]
}
// 支持键盘快捷键
import SwiftUI

struct KeyboardShortcutView: View {
    @State private var showNewDocument = false
    @State private var searchText = ""
    
    var body: some View {
        ContentView()
            .searchable(text: $searchText)  // Cmd+F 自动聚焦搜索栏
            .keyboardShortcut("n", modifiers: .command) {
                // Cmd+N: 新建文档
                showNewDocument = true
            }
            .keyboardShortcut("w", modifiers: .command) {
                // Cmd+W: 关闭当前窗口
                // 由系统自动处理
            }
            .keyboardShortcut(",", modifiers: .command) {
                // Cmd+,: 打开设置
                openSettings()
            }
            .sheet(isPresented: $showNewDocument) {
                NewDocumentView()
            }
    }
    
    func openSettings() {
        // 导航到设置页面
    }
}
// 自定义 Scribble 支持的文本输入
import SwiftUI
import PencilKit

struct CustomTextField: UIViewRepresentable {
    @Binding var text: String
    var placeholder: String
    
    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField()
        textField.placeholder = placeholder
        textField.delegate = context.coordinator
        // Scribble 自动支持,无需额外配置
        // 但可以在 delegate 中拦截输入做自定义处理
        return textField
    }
    
    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(text: $text)
    }
    
    class Coordinator: NSObject, UITextFieldDelegate {
        @Binding var text: String
        
        init(text: Binding<String>) {
            _text = text
        }
        
        func textFieldDidChangeSelection(_ textField: UITextField) {
            text = textField.text ?? ""
        }
    }
}

最佳实践

  • iPad 应用采用 Sidebar 导航而非底部 Tab Bar,充分利用宽屏空间
  • 支持多窗口时确保每个窗口有独立的导航栈,共享数据通过单一数据源管理
  • 为所有主要操作提供键盘快捷键,遵循 Apple 的标准快捷键约定
  • 测试 Scribble 在自定义文本控件中的表现,特别是有输入验证的表单
  • 在 iPad 和 iPad mini 两种屏幕尺寸上测试布局,确保 compact/expanded 状态都正常

还有什么值得关注

  • iPadOS 14 的 Sidebar 可以通过设置 navigationSplitViewColumnWidth 自定义宽度
  • Compact sidebar 模式下点击图标会自动展开 sidebar,用户不需要手动切换
  • Apple Pencil 的双击手势(切换工具)可以在应用中自定义,通过 UIPencilInteraction 实现
WWDC 2020