©️ OverlookArt

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}

如果 Flavorid 与选择类型匹配(在此情况下,都是 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 描述选择目的的字符串(LocalizedStringKeyStringProtocol
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}

工作原理:

  1. sources 接收一个绑定集合 $selectedObjectBorders
  2. selection 指向集合中每个元素的 thickness 属性
  3. 当用户选择新值时,集合中所有对象的 thickness 属性都会更新为新值
  4. 如果集合中各对象的 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(_:) 修饰符显式指定标签。
  • 对于选项较少的情况,考虑使用 inlinesegmented 样式以获得更好的用户体验。
  • 在需要导航到新页面进行选择的场景中,可使用 navigationLink 样式。