Picker
Picker 是一个用户界面控件,允许用户从一组互斥选项中选择一个值。它根据上下文和可用选项的数量以不同的样式呈现,例如菜单、分段控件或滚轮。
创建 Picker
您可以通过提供选择绑定、标签以及要显示的内容来创建 Picker。将 selection 参数设置为绑定属性,以提供当前选择的值。将标签设置为描述选择目的的视图,然后提供 picker 要显示的内容。
基本初始化方法
1/// 创建一个从局部化字符串键生成标签的选择器。
2init(_ titleKey: LocalizedStringKey, selection: Binding<Value>, @ViewBuilder content: () -> Content)
3
4/// 创建一个显示自定义标签的选择器。
5init(selection: Binding<Value>, @ViewBuilder content: () -> Content, label: () -> Label)
示例:基本选择器
考虑一个表示冰淇淋口味的枚举:
1enum Flavor: String, CaseIterable, Identifiable {
2 case chocolate, vanilla, strawberry
3
4 var id: String { rawValue }
5}
使用 State 属性来存储选择:
1struct FlavorPicker: View {
2 @State private var selectedFlavor = Flavor.chocolate
3
4 var body: some View {
5 Picker("口味", selection: $selectedFlavor) {
6 Text("巧克力").tag(Flavor.chocolate)
7 Text("香草").tag(Flavor.vanilla)
8 Text("草莓").tag(Flavor.strawberry)
9 }
10 }
11}
提供选择选项
通常,您会使用 ForEach 从一个集合中生成 picker 的内容。如果集合中的元素遵循 Identifiable 协议,则可以省略显式的 tag 修饰符,因为 ForEach 会自动使用每个元素的 id 作为标签。
使用 ForEach
1Picker("口味", selection: $selectedFlavor) {
2 ForEach(Flavor.allCases) { flavor in
3 Text(flavor.rawValue.capitalized).tag(flavor)
4 }
5}
如果 Flavor 的 id 与选择类型匹配(在此情况下,都是 String),则可以省略 .tag(flavor):
1Picker("口味", selection: $selectedFlavor) {
2 ForEach(Flavor.allCases) { flavor in
3 Text(flavor.rawValue.capitalized)
4 }
5}
Image 标签
使用 init(_:image:selection:content:) 初始化方法可以创建同时包含文本和图片标签的选择器。
方法签名
1// 使用 LocalizedStringKey(支持本地化)
2@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, *)
3nonisolated init(
4 _ titleKey: LocalizedStringKey,
5 image: ImageResource,
6 selection: Binding<SelectionValue>,
7 @ViewBuilder content: () -> Content
8)
9
10// 使用 StringProtocol(支持任意字符串类型)
11@available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, *)
12nonisolated init<S>(
13 _ title: S,
14 image: ImageResource,
15 selection: Binding<SelectionValue>,
16 @ViewBuilder content: () -> Content
17) where S : StringProtocol
参数说明
| № | 参数 | 说明 |
|---|---|---|
| 0 | titleKey / title |
描述选择目的的字符串(LocalizedStringKey 或 StringProtocol) |
| 1 | image |
图片资源(ImageResource),通常来自 Asset Catalog |
| 2 | selection |
绑定到当前选中选项的属性 |
| 3 | content |
包含选项内容的视图 |
示例
1import SwiftUI
2
3enum Berry: String, CaseIterable, Identifiable {
4 case blueberry = "蓝莓"
5 case raspberry = "覆盆子"
6 case strawberry = "草莓"
7
8 var id: String { rawValue }
9}
10
11struct BerryPicker: View {
12 @State private var selectedBerry = Berry.blueberry
13
14 var body: some View {
15 // 假设在 Asset Catalog 中有一张名为 "berries" 的图片
16 Picker("选择浆果", image: "berries", selection: $selectedBerry) {
17 ForEach(Berry.allCases) { berry in
18 Text(berry.rawValue).tag(berry)
19 }
20 }
21 }
22}
SF Symbols 方案
1// 使用 SF Symbol
2Picker(selection: $selectedFlavor) {
3 ForEach(Flavor.allCases) { flavor in
4 Text(flavor.rawValue.capitalized).tag(flavor)
5 }
6} label: {
7 Label("口味", systemImage: "drop.fill")
8}
9
10// 使用自定义视图
11Picker(selection: $selectedFlavor) {
12 ForEach(Flavor.allCases) { flavor in
13 Text(flavor.rawValue.capitalized).tag(flavor)
14 }
15} label: {
16 HStack {
17 Image(systemName: "drop.fill")
18 .foregroundStyle(.blue)
19 Text("口味")
20 }
21}
绑定到一组选择项
使用 init(_:sources:selection:content:) 初始化方法可以创建一个绑定到绑定集合的 Picker。当你需要同时控制多个对象的同一属性时,这个方法非常有用。
方法签名
1@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
2init<C, S>(
3 _ title: S,
4 sources: C,
5 selection: KeyPath<C.Element, Binding<SelectionValue>>,
6 @ViewBuilder content: () -> Content
7) where C : RandomAccessCollection, S : StringProtocol
参数说明
| № | 参数 | 说明 |
|---|---|---|
| 0 | title |
描述选择目的的字符串 |
| 1 | sources |
用作 Picker 数据源的集合 |
| 2 | selection |
指向当前选中选项的键路径。当用户选择某个选项时,集合中所有元素在该键路径处的值都会更新 |
| 3 | content |
包含选项内容的视图 |
混合状态
如果传递给 sources 的集合中包装的值不完全相同,某些样式会以混合状态呈现选择。具体表现取决于样式。例如,使用菜单样式的 Picker 会显示短横线(—)而不是勾选标记来表示选中值。
示例:管理多个对象的边框粗细
1import SwiftUI
2
3enum Thickness: String, CaseIterable, Identifiable {
4 case thin = "细"
5 case regular = "中"
6 case thick = "粗"
7
8 var id: String { rawValue }
9}
10
11struct Border {
12 var color: Color
13 var thickness: Thickness
14}
15
16struct InspectorView: View {
17 @State private var selectedObjectBorders = [
18 Border(color: .black, thickness: .thin),
19 Border(color: .red, thickness: .thick)
20 ]
21
22 var body: some View {
23 VStack {
24 // 绑定到 selectedObjectBorders 数组中的 thickness 属性
25 Picker(
26 "边框粗细",
27 sources: $selectedObjectBorders,
28 selection: \.thickness
29 ) {
30 ForEach(Thickness.allCases) { thickness in
31 Text(thickness.rawValue)
32 }
33 }
34 .pickerStyle(.menu)
35
36 // 显示当前选中的粗细值
37 Text("选中的粗细: \(selectedObjectBorders[0].thickness.rawValue)")
38 }
39 .padding()
40 }
41}
工作原理:
sources接收一个绑定集合$selectedObjectBordersselection指向集合中每个元素的thickness属性- 当用户选择新值时,集合中所有对象的
thickness属性都会更新为新值 - 如果集合中各对象的
thickness值不相同,Picker 会显示混合状态
带自定义标签的版本
如果你需要自定义标签视图,可以使用 init(sources:selection:content:label:) 方法:
1Picker(
2 sources: $selectedObjectBorders,
3 selection: \.thickness
4) {
5 ForEach(Thickness.allCases) { thickness in
6 Text(thickness.rawValue)
7 }
8} label: {
9 Label("边框粗细", systemImage: "line.3.horizontal")
10}
使用场景
- 批量编辑:同时编辑多个选中对象的同一属性
- 文档检查器:控制当前选中的多个形状的边框、颜色等属性
- 表格单元格:统一管理多行数据的同一字段值
修饰符
您可以使用 pickerStyle(_:) 修饰符来自定义选择器的外观和交互方式。SwiftUI 提供了几种符合 PickerStyle 协议的内置样式。
可用样式
| № | 样别 | 说明 | 平台支持 |
|---|---|---|---|
| 0 | automatic |
默认样式,由系根据上下文自动选择 | 所有平台 |
| 1 | menu |
菜单样式,点击后弹出选项列表 | iOS, macOS, etc. |
| 2 | segmented |
分段控件样式,显示为水平分段 | iOS, macOS, tvOS |
| 3 | inline |
内联样式,选项在一行内显示(适用于少量选项) | 所有平台 |
| 4 | wheel |
滚轮样式,类似于 UIDatePicker 的滚轮 |
iOS, watchOS |
| 5 | palette |
调色板样式,显示为按钮组(常用于颜色选择) | macOS |
| 6 | navigationLink |
导航链接样式,选择时推送新视图 | iOS, macOS(在导航视图中) |
应用样式
1Picker("口味", selection: $selectedFlavor) {
2 ForEach(Flavor.allCases) { flavor in
3 Text(flavor.rawValue.capitalized).tag(flavor)
4 }
5}
6.pickerStyle(.segmented)
平台支持
| № | 平台 | 最低版本 |
|---|---|---|
| 0 | iOS | 13.0+ |
| 1 | iPadOS | 13.0+ |
| 2 | Mac Catalyst | 13.0+ |
| 3 | macOS | 10.15+ |
| 4 | tvOS | 13.0+ |
| 5 | watchOS | 6.0+ |
| 6 | visionOS | 1.0+ |
完整示例
以下示例展示了一个带有自定义标签、使用 ForEach 生成选项并应用分段样式的选择器:
1import SwiftUI
2
3enum Theme: String, CaseIterable, Identifiable {
4 case light = "浅色"
5 case dark = "深色"
6 case system = "系统默认"
7
8 var id: String { rawValue }
9}
10
11struct ThemePicker: View {
12 @State private var selectedTheme = Theme.system
13
14 var body: some View {
15 Form {
16 Section(header: Text("外观")) {
17 Picker("主题", selection: $selectedTheme) {
18 ForEach(Theme.allCases) { theme in
19 Text(theme.rawValue).tag(theme)
20 }
21 }
22 .pickerStyle(.segmented)
23 }
24 }
25 .padding()
26 }
27}
注意事项
- 选择器的外观高度依赖于其使用的容器(如
List,Form)和平台。 - 确保选择项的标签类型与绑定的
Value类型匹配,否则需要使用tag(_:)修饰符显式指定标签。 - 对于选项较少的情况,考虑使用
inline或segmented样式以获得更好的用户体验。 - 在需要导航到新页面进行选择的场景中,可使用
navigationLink样式。