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 直接挂到 UIButton 与 UIBarButtonItem,长按或触摸即可打开。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:34)UIPageControl 的新外观允许控件在固定尺寸内支持任意页数;空间不足时,用户可以在页点之间 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:56)UIColorPickerViewController 是一个可以以 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监听值变化。
Menus 可以异步生成,也可以更新正在展示的内容
(14:20)iOS 14 让 UIButton 和 UIBarButtonItem 直接支持 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)已经打开的菜单也能更新。UIContextMenuInteraction 的 updateVisibleMenu 会传入当前菜单的副本,返回新的菜单用于替换当前展示内容。
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:05)UIAction 在 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:41)UISegmentedControl 也支持由 UIAction 创建 segment。每个 segment 的 handler 紧贴配置位置,用户选择某个 segment 时只调用对应 action。演讲建议用 enum 生成 actions,这样新增 case 时配置与响应逻辑会一起更新。
核心启发
-
做一个支持品牌色的编辑器设置页:用
UIColorPickerViewController让用户选择主题色。它已经处理网格、光谱、RGB、十六进制、收藏色和吸管;实现入口是展示 picker、设置selectedColor,在 delegate 回调里保存选择。 -
重做表单里的日期字段:把占空间的滚轮改成
UIDatePickercompact 样式。它适合 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
- Design with iOS pickers, menus and actions — 这场设计配套 session 讲 pickers、menus 和 actions 应该放在什么场景里使用。
- Optimize the interface of your Mac Catalyst app — 本 session 多次提到 Catalyst 外观和菜单,继续看它可以处理 Mac 端界面适配。
- Build for the iPadOS pointer — Date Picker 与标准控件在 iPad 上要兼顾指针输入,这场 session 讲 pointer interaction 的设计和 API。
- Handle trackpad and mouse input — 如果 App 面向 iPad 或 Mac Catalyst 用户,这场 session 补充 trackpad、mouse、scroll 和 gesture 处理。
评论
GitHub Issues · utterances