WWDC Quick Look 💓 By SwiftGGTeam
Build with iOS pickers, menus and actions

Build with iOS pickers, menus and actions

观看原视频

Highlight

iOS 14 让 UIKit 的 Page Control、Color Picker、Date Picker、Menus 和 UIAction 获得新的系统级行为,开发者可以用现成控件处理颜色选择、紧凑日期输入、异步菜单和统一事件回调。


核心内容

这场 session 解决的是一个很常见的界面问题:App 里有很多小交互,开发者常常自己拼。分页指示器要支持很多页,颜色选择要支持吸管和收藏色,日期输入要塞进表单,按钮旁边还要挂一组上下文操作。自己做这些功能,容易把交互细节、无障碍、指针输入和 Catalyst 外观都漏掉。(00:24

Apple 在 iOS 14 把这些交互收进 UIKit 控件里。UIPageControl 支持固定宽度内的无限页数和自定义指示图片。UIColorPickerViewController 负责颜色网格、光谱、RGB、十六进制、收藏色和吸管。UIDatePicker 增加 compact 与 inline 样式,表单和大屏都能用同一套 API。(03:20

后半段重点转向菜单和 action。iOS 14 允许把 UIMenu 直接挂到 UIButtonUIBarButtonItem,长按或触摸即可打开。UIDeferredMenuElement 让菜单项可以异步生成,updateVisibleMenu 让已经展示的菜单可以更新。UIAction 则把按钮、Bar Button Item、Segmented Control 的事件处理放到创建控件的位置,减少分散的 target-action 代码。(11:15

这些能力也服务于 iPad 与 Mac Catalyst。演讲里多次提到标准控件会在优化后的 Catalyst App 中采用更接近 macOS 的外观;Date Picker 的 compact 样式也适合指针输入。开发者少写自定义控件,App 更容易跟上系统平台差异。(01:38


详细内容

Page Control 可以在固定空间里承载更多页

04:34UIPageControl 的新外观允许控件在固定尺寸内支持任意页数;空间不足时,用户可以在页点之间 scrub 和滚动。它还增加了指示图片和背景样式定制,适合书签页、收藏页这类有特殊含义的页面。

let pageControl = UIPageControl()
pageControl.numberOfPages = 5

pageControl.backgroundStyle = .prominent

pageControl.preferredIndicatorImage =
    UIImage(systemName: "bookmark.fill")

pageControl.setIndicatorImage(
    UIImage(systemName: "heart.fill"), forPage: 0)

关键点:

  • numberOfPages 仍然使用原来的 API,迁移成本低。
  • backgroundStyle = .prominent 让背景区域持续显示,适合把 Page Control 当作主要控件的界面。
  • preferredIndicatorImage 设置所有页的默认图片。
  • setIndicatorImage(_:forPage:) 可以给某一页单独设置图标,演讲用第 0 页的心形表示收藏。

Color Picker 负责完整的颜色选择流程

06:56UIColorPickerViewController 是一个可以以 sheet 或 popover 呈现的 view controller。用户可以从网格、光谱、RGB、十六进制值中选色,也可以收藏常用颜色。iPadOS 上的吸管可以从屏幕任意位置取色,Mac Catalyst 上会使用标准 macOS color panel。

var color = UIColor.blue
var colorPicker = UIColorPickerViewController()

func pickColor() {
    colorPicker.supportsAlpha = true
    colorPicker.selectedColor = color
    self.present(colorPicker,
        animated: true,
      completion: nil)
}

func colorPickerViewControllerDidSelectColor(_
  viewController: UIColorPickerViewController) {
    color = viewController.selectedColor
}

func colorPickerViewControllerDidFinish(_
  viewController: UIColorPickerViewController) {
    // Do nothing
}

关键点:

  • supportsAlpha 控制是否允许透明度。
  • selectedColor 在展示前写入当前颜色,用户打开后能看到现有值。
  • present 负责展示系统颜色选择器,App 不需要自己实现吸管或收藏色。
  • colorPickerViewControllerDidSelectColor 在用户选中颜色后读取 selectedColor
  • 用户取消时可以不处理,演讲中的 DidFinish 示例明确选择了空实现。

Date Picker 用同一套 API 切换 compact 和 inline 样式

10:04)iOS 14 和 iPadOS 14 的 UIDatePicker 增加 compact 样式。它把日期和时间显示成字段,点日期打开日历,点时间打开键盘输入,适合 table view 或表单中的紧凑布局。inline 样式适合日期选择是界面主任务的场景。演讲强调,新样式不会改变 UIDatePicker 的基础 API。

let datePicker = UIDatePicker()
datePicker.date = Date(timeIntervalSinceReferenceDate:
                       timeInterval)

datePicker.preferredDatePickerStyle = .compact

datePicker.calendar = Calendar(identifier: .japanese)
datePicker.datePickerMode = .date

datePicker.addTarget(self,
             action: #selector(dateSet),
                for: .valueChanged)

关键点:

  • date 继续使用原来的方式设置初始值。
  • preferredDatePickerStyle = .compact 只改变展示方式。
  • calendar 可以换成日本历等日历系统,新样式会显示对应内容。
  • datePickerMode = .date 把选择范围限制为日期,隐藏不相关的时间输入。
  • addTarget(_:action:for:) 仍然把它当作 UIControl 监听值变化。

14:20)iOS 14 让 UIButtonUIBarButtonItem 直接支持 menu。默认长按展示菜单;UIButton 可以通过 showsMenuAsPrimaryAction 在触摸时立即展示;UIBarButtonItem 在有 menu 且没有 primary action 时也会触摸即展示。复杂菜单可以用 UIDeferredMenuElement 延迟生成。

button.menu = UIMenu(title: "", children: [
    UIMenu(title: "", options: .displayInline, children: (1...2).map { UIAction(title: "Static Item \($0)") { action in }}),
    UIDeferredMenuElement({ completion in
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            completion([UIMenu(title: "", options: .displayInline, children: (1...2).map { UIAction(title: "Dynamic Item \($0)") { action in }})])
        }
    }),
])

关键点:

  • button.menu 把菜单直接挂在按钮上,UIKit 负责展示。
  • 第一个 UIMenu 放静态项,options: .displayInline 让它们内联显示。
  • UIDeferredMenuElement 的闭包异步调用 completion,适合等数据准备好再补菜单项。
  • UIKit 会在等待期间显示标准 loading UI,并在后续展示时缓存已经生成的项目。

14:50)已经打开的菜单也能更新。UIContextMenuInteractionupdateVisibleMenu 会传入当前菜单的副本,返回新的菜单用于替换当前展示内容。

self.contextMenuInteraction.updateVisibleMenu { currentMenu -> UIMenu in
    currentMenu.children.forEach { element in
        guard let action = element as? UIAction else { return }

        action.state = Bool.random() ? .off : .on
        action.attributes = Bool.random() ? [.hidden] : []
    }
    return currentMenu
}

关键点:

  • updateVisibleMenu 只处理正在展示给用户的菜单。
  • currentMenu.children 可以遍历当前菜单项。
  • 示例只更新 UIAction,其他 menu element 直接跳过。
  • state 可以切换选中状态,attributes 可以隐藏项目。
  • iOS 14 中 UIMenu 不再强制 children 不可变,因此可以更新传入菜单后直接返回。

UIAction 把事件处理放回控件创建现场

16:05UIAction 在 iOS 13 引入,iOS 14 扩展到更多控件。UIBarButtonItem 新增 initializer,可以创建只执行 action 的按钮、只展示 menu 的按钮,或同时支持 tap action 与 long-press menu 的按钮。

let saveAction = UIAction(title: "") { action in }
let saveMenu = UIMenu(title: "", children: [
    UIAction(title: "Copy", image: UIImage(systemName: "doc.on.doc")) { action in },
    UIAction(title: "Rename", image: UIImage(systemName: "pencil")) { action in },
    UIAction(title: "Duplicate", image: UIImage(systemName: "plus.square.on.square")) { action in },
    UIAction(title: "Move", image: UIImage(systemName: "folder")) { action in },
])
let optionsImage = UIImage(systemName: "ellipsis.circle")
let optionsMenu = UIMenu(title: "", children: [
    UIAction(title: "Info", image: UIImage(systemName: "info.circle")) { action in },
    UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { action in },
    UIAction(title: "Collaborate", image: UIImage(systemName: "person.crop.circle.badge.plus")) { action in },
])
let revertAction = UIAction(title: "Revert") { action in }
self.toolbarItems = [
    UIBarButtonItem(systemItem: .save, primaryAction: saveAction, menu: saveMenu),
    .fixedSpace(width:20.0),
    UIBarButtonItem(image: optionsImage, menu: optionsMenu),
    .flexibleSpace(),
    UIBarButtonItem(primaryAction: revertAction),
]

关键点:

  • primaryAction 处理点击,menu 处理更多操作。
  • UIAction 可以同时作为菜单项,也可以作为 Bar Button Item 的主操作。
  • 有 image 和 menu、没有 primary action 的 Bar Button Item 会在触摸时展示菜单。
  • .fixedSpace(width:).flexibleSpace() 是演讲中展示的新 toolbar item 配置方式。
  • 同一个 toolbar 可以混合保存、更多选项和撤销这几类操作。

17:41UISegmentedControl 也支持由 UIAction 创建 segment。每个 segment 的 handler 紧贴配置位置,用户选择某个 segment 时只调用对应 action。演讲建议用 enum 生成 actions,这样新增 case 时配置与响应逻辑会一起更新。


核心启发

  • 做一个支持品牌色的编辑器设置页:用 UIColorPickerViewController 让用户选择主题色。它已经处理网格、光谱、RGB、十六进制、收藏色和吸管;实现入口是展示 picker、设置 selectedColor,在 delegate 回调里保存选择。

  • 重做表单里的日期字段:把占空间的滚轮改成 UIDatePicker compact 样式。它适合 table view 或多字段表单;实现入口是设置 preferredDatePickerStyle = .compact,再用 datePickerMode 限制只选日期或只选时间。

  • 给工具栏按钮补上长按菜单:保存、更多、分享这类按钮可以同时有 primary action 和 menu。实现入口是用 UIBarButtonItem(systemItem:primaryAction:menu:)UIBarButtonItem(image:menu:) 组织常用动作和扩展动作。

  • 把需要网络数据的菜单改成延迟加载:文件夹、项目列表、协作者列表这类菜单项可以用 UIDeferredMenuElement 生成。用户打开菜单后先看到系统 loading UI,数据回来后调用 completion 填充 menu。

  • 让 Segmented Control 的选项和处理逻辑靠在一起:用 UIAction 创建每个 segment,避免用 selectedSegmentIndex 写 switch。入口是用 enum 生成一组 actions,再交给 UISegmentedControl 的新 initializer。

关联 Session

评论

GitHub Issues · utterances