Design with iOS pickers, menus and actions
Design 入门 22m

使用 iOS 选择器、菜单和操作进行设计

Design with iOS pickers, menus and actions

2020年6月23日

在 Apple 官方观看视频

一句话判断

iOS 14 重新定义了选择器、菜单和操作交互的 UI 范式——如果你还在用 Action Sheet 和 PickerView 做”选择”类交互,这场 Session 会告诉你为什么该换成 Menu 和 DatePicker 的新模式。

这场 Session 讲了什么

iOS 14 对多个交互组件进行了设计更新,Session 详细介绍了 Date Picker(日期选择器)、Color Picker(颜色选择器)、Context Menu(上下文菜单)和 Action 的新设计规范。这些变化的共同主题是”减少干扰、保持上下文”。

Date Picker 迎来了最大的变化。以前的全屏滚轮式日期选择器被替换为紧凑的 inline 样式——一个看起来像普通文本框的控件,点击后展开为日历视图或滚轮。这个设计变化的原因是全屏选择器打断了用户的操作流——选个日期要覆盖整个屏幕,太重了。新的 compact 样式只占用一行空间,展开后也只在当前视图上方浮层展示,不影响页面其他内容。

Color Picker 是全新的系统组件,提供了统一的颜色选择体验:包含网格色板、滑块和吸管工具。如果你的应用需要用户选择颜色,直接用系统的 UIColorPickerViewController,它已经处理了不同的色彩空间(sRGB、Display P3)和无障碍支持。

Context Menu(长按菜单)获得了增强,支持嵌套子菜单和更大的预览图。Session 展示了如何通过 UIContextMenuInteraction 创建多级菜单结构。

值得深挖的点

Compact Date Picker 的交互哲学

新的 compact date picker 体现了 Apple 的”轻量化交互”设计哲学:用户期望的操作应该用最少的界面面积完成。选择一个日期不应该是一个”事件”,而应该像选择一个下拉选项一样自然。compact 模式下,日历视图从按钮位置弹出,选择后自动收回。这种”点即选”的体验比以前”打开新页面 -> 滚轮选择 -> 点确认”流畅得多。

Menu 替代 Action Sheet 的趋势

iOS 14 中,越来越多的”选择”场景推荐使用 Menu 替代 Action Sheet。Menu 是一个轻量级的浮层菜单,从触发点弹出,不遮挡全部屏幕。Action Sheet 则是底部弹出的模态面板,需要用户明确操作才能关闭。Session 建议:2-5 个选项的场景用 Menu,需要用户确认的重要操作(如删除)用 Action Sheet。

代码片段

import SwiftUI

// 新的 Date Picker 样式
struct DatePickerExample: View {
    @State private var selectedDate = Date()
    @State private var birthDate = Date()
    
    var body: some View {
        Form {
            // Compact 样式(默认)—— 一行空间,点击展开
            DatePicker(
                "选择日期",
                selection: $selectedDate,
                displayedComponents: [.date]
            )
            // 自动使用 GraphicalDatePickerStyle
            
            // Graphical 样式——直接展示日历
            DatePicker(
                "生日",
                selection: $birthDate,
                in: ...Date(),  // 不能选择未来日期
                displayedComponents: [.date]
            )
            .datePickerStyle(.graphical)
            
            // Wheel 样式(经典滚轮)
            DatePicker(
                "时间",
                selection: $selectedDate,
                displayedComponents: [.hourAndMinute]
            )
            .datePickerStyle(.wheel)
        }
    }
}
// 新的 Color Picker
import SwiftUI

struct ColorPickerExample: View {
    @State private var selectedColor: Color = .blue
    @State private var showColorPicker = false
    
    var body: some View {
        VStack(spacing: 20) {
            // SwiftUI 原生颜色选择器
            ColorPicker(
                "选择颜色",
                selection: $selectedColor,
                supportsOpacity: true  // 支持透明度调节
            )
            
            // 展示选中颜色的效果
            RoundedRectangle(cornerRadius: 12)
                .fill(selectedColor)
                .frame(width: 200, height: 100)
                .overlay(
                    Text("预览")
                        .foregroundColor(.white)
                        .bold()
                )
        }
        .padding()
    }
}
// Context Menu 与嵌套菜单
import SwiftUI

struct MenuExample: View {
    @State private var selectedSort = "默认排序"
    
    var body: some View {
        List {
            // Menu 按钮(轻量级替代 Action Sheet)
            Menu {
                // 嵌套子菜单
                Menu("排序方式") {
                    Button("按名称") { selectedSort = "名称" }
                    Button("按日期") { selectedSort = "日期" }
                    Button("按大小") { selectedSort = "大小" }
                }
                
                Divider()
                
                Button("刷新数据") {
                    refreshData()
                }
                
                Button("全选") {
                    selectAll()
                }
                
                Divider()
                
                // 危险操作用红色文字
                Button(role: .destructive) {
                    deleteAll()
                } label: {
                    Label("删除全部", systemImage: "trash")
                }
            } label: {
                Label("操作", systemImage: "ellipsis.circle")
            }
            
            Text("当前排序: \(selectedSort)")
        }
        .contextMenu {
            // 长按触发的 Context Menu
            Button {
                copyItem()
            } label: {
                Label("复制", systemImage: "doc.on.doc")
            }
            
            Button {
                shareItem()
            } label: {
                Label("分享", systemImage: "square.and.arrow.up")
            }
            
            Divider()
            
            Button(role: .destructive) {
                deleteItem()
            } label: {
                Label("删除", systemImage: "trash")
            }
        }
    }
    
    func refreshData() {}
    func selectAll() {}
    func deleteAll() {}
    func copyItem() {}
    func shareItem() {}
    func deleteItem() {}
}

最佳实践

  • 新项目默认使用 compact date picker,只在需要”沉浸式”日期选择场景(如日历应用)时使用 graphical 样式
  • 2-5 个选项的”选择”场景用 Menu,超过 5 个选项考虑用 List 或专门的筛选页
  • 使用系统 Color Picker 而非自建颜色选择器——系统组件自动适配 Dark Mode 和无障碍
  • Context Menu 中使用图标(SF Symbols)增强可识别性,每个选项配一个图标
  • 危险操作(删除、重置)使用 .destructive role,系统会自动用红色文字标记

还有什么值得关注

  • compact date picker 在 iOS 14 中是 DatePicker 的默认样式,不需要额外配置
  • Color Picker 支持 Display P3 广色域,在设计类应用中特别有用
  • SwiftUI 的 Menu 在 iPad 上以 popover 形式展示,在 iPhone 上以 action sheet 形式展示,系统自动适配
WWDC 2020