以下内容已整理到小册子中,本文会随着系统更新和我更多的实践而新增和更新,你可以下载“戴铭的开发小册子”应用,来跟踪查看本文内容新增和更新。小册子应用的代码可以在 Github 上查看。
本文属于小册子系列中的一篇,已发布系列文章有:
- 小册子之如何使用 SwiftData 开发 SwiftUI 应用
- 小册子之简说 Widget 小组件
- 小册子之 List、Lazy 容器、ScrollView、Grid 和 Table 数据集合 SwiftUI 视图
- 小册子之详说 Navigation、ViewThatFits、Layout 协议等布局 SwiftUI 组件
- 【本篇】小册子之 Form、Picker、Toggle、Slider 和 Stepper 表单相关 SwiftUI 视图
- 小册子之 SwiftUI 动画
Form
| 控件视图 | 说明 | Style | 
|---|---|---|
| Button | 触发操作的按钮 | .bordered, .borderless, .borderedProminent, .plain | 
| Picker | 提供多选项供选择 | .wheel, .inline, .segmented, .menu, .radioGroup | 
| DatePicker and MultiDatePicker | 选择日期的工具 | .compact, .wheel, .graphical | 
| Toggle | 切换两种状态的开关 | .switch, .botton, .checkbox | 
| Stepper | 调整数值的步进器 | 无样式选项 | 
| Menu | 显示选项列表的菜单 | .borderlessButton, .button | 
Form 有 ColumnFormStyle 还有 GroupedFormStyle。使用 buttonStyle 修饰符:
Form {
   ...
}.formStyle(.grouped)Form 新版也得到了增强,示例如下:
struct SimpleFormView: View {
    @State private var date = Date()
    @State private var eventDescription = ""
    @State private var accent = Color.red
    @State private var scheme = ColorScheme.light
    var body: some View {
        Form {
            Section {
                DatePicker("Date", selection: $date)
                TextField("Description", text: $eventDescription)
                    .lineLimit(3)
            }
            
            Section("Vibe") {
                Picker("Accent color", selection: $accent) {
                    ForEach(Color.accentColors, id: \.self) { color in
                        Text(color.description.capitalized).tag(color)
                    }
                }
                Picker("Color scheme", selection: $scheme) {
                    Text("Light").tag(ColorScheme.light)
                    Text("Dark").tag(ColorScheme.dark)
                }
            }
        }
        .formStyle(.grouped)
    }
}
extension Color {
    static let accentColors: [Color] = [.red, .green, .blue, .orange, .pink, .purple, .yellow]
}Form 的样式除了 .formStyle(.grouped) 还有 .formStyle(..columns)。
关于 Form 字体、单元、背景颜色设置,参看下面代码:
struct ContentView: View {
    @State private var movieTitle = ""
    @State private var isWatched = false
    @State private var rating = 1
    @State private var watchDate = Date()
    var body: some View {
        Form {
            Section {
                TextField("电影标题", text: $movieTitle)
                LabeledContent("导演", value: "克里斯托弗·诺兰")
            } header: {
                Text("关于电影")
            }
            .listRowBackground(Color.gray.opacity(0.1))
            Section {
                Toggle("已观看", isOn: $isWatched)
                Picker("评分", selection: $rating) {
                    ForEach(1...5, id: \.self) { number in
                        Text("\(number) 星")
                    }
                }
            } header: {
                Text("电影详情")
            }
            .listRowBackground(Color.gray.opacity(0.1))
            
            Section {
                DatePicker("观看日期", selection: $watchDate)
            }
            .listRowBackground(Color.gray.opacity(0.1))
            
            Section {
                Button("重置所有电影数据") {
                    resetAllData()
                }
            }
            .listRowBackground(Color.white)
        }
        .foregroundColor(.black)
        .tint(.indigo)
        .background(Color.yellow)
        .scrollContentBackground(.hidden)
        .navigationBarTitle("电影追踪器")
    }
    
    private func resetAllData() {
        movieTitle = ""
        isWatched = false
        rating = 1
        watchDate = Date()
    }
}
struct LabeledContent: View {
    let label: String
    let value: String
    init(_ label: String, value: String) {
        self.label = label
        self.value = value
    }
    var body: some View {
        HStack {
            Text(label)
            Spacer()
            Text(value)
        }
    }
}Picker选择器
Picker

SwiftUI 中的 Picker 视图是一个用于选择列表中的一个选项的用户界面元素。你可以使用 Picker 视图来创建各种类型的选择器,包括滚动选择器、弹出菜单和分段控制。
示例代码如下:
struct PlayPickerView: View {
    @State private var select = 1
    @State private var color = Color.red.opacity(0.3)
    
    var dateFt: DateFormatter {
        let ft = DateFormatter()
        ft.dateStyle = .long
        return ft
    }
    @State private var date = Date()
    
    var body: some View {
        
        // 默认是下拉的风格
        Form {
            Section("选区") {
                Picker("选一个", selection: $select) {
                    Text("1")
                        .tag(1)
                    Text("2")
                        .tag(2)
                }
            }
        }
        .padding()
        
        // Segment 风格,
        Picker("选一个", selection: $select) {
            Text("one")
                .tag(1)
            Text("two")
                .tag(2)
        }
        .pickerStyle(SegmentedPickerStyle())
        .padding()
        
        // 颜色选择器
        ColorPicker("选一个颜色", selection: $color, supportsOpacity: false)
            .padding()
        
        RoundedRectangle(cornerRadius: 8)
            .fill(color)
            .frame(width: 50, height: 50)
        
        // 时间选择器
        VStack {
            DatePicker(selection: $date, in: ...Date(), displayedComponents: .date) {
                Text("选时间")
            }
            
            DatePicker("选时间", selection: $date)
                .datePickerStyle(GraphicalDatePickerStyle())
                .frame(maxHeight: 400)
            
            Text("时间:\(date, formatter: dateFt)")
        }
        .padding()
    }
}上面的代码中,有三种类型的 Picker 视图:
- 默认的下拉风格 Picker视图。这种类型的Picker视图在Form中使用,用户可以点击选择器来打开一个下拉菜单,然后从菜单中选择一个选项。
Form {
    Section("选区") {
        Picker("选一个", selection: $select) {
            Text("1")
                .tag(1)
            Text("2")
                .tag(2)
        }
    }
}- 分段控制风格 Picker视图。这种类型的Picker视图使用SegmentedPickerStyle()修饰符,它将选择器显示为一组水平排列的按钮,用户可以点击按钮来选择一个选项。
Picker("选一个", selection: $select) {
    Text("one")
        .tag(1)
    Text("two")
        .tag(2)
}
.pickerStyle(SegmentedPickerStyle())- ColorPicker和- DatePicker视图。这两种类型的视图是- Picker视图的特殊形式,它们分别用于选择颜色和日期。
ColorPicker("选一个颜色", selection: $color, supportsOpacity: false)
DatePicker("选时间", selection: $date)
    .datePickerStyle(GraphicalDatePickerStyle())在所有这些 Picker 视图中,你都需要提供一个绑定的选择状态,这个状态会在用户选择一个新的选项时更新。你还需要为每个选项提供一个视图和一个唯一的标签。
文字Picker
基本使用
文字 Picker 示例:
struct StaticDataPickerView: View {
    @State private var selectedCategory = "动作"
    var body: some View {
        VStack {
            Text("选择的类别: \(selectedCategory)")
            Picker("电影类别",
                 selection: $selectedCategory) {
                Text("动作")
                    .tag("动作")
                Text("喜剧")
                    .tag("喜剧")
                Text("剧情")
                    .tag("剧情")
                Text("恐怖")
                    .tag("恐怖")
            }
        }
    }
}使用枚举
使用枚举来创建选取器的示例:
enum MovieCategory: String, CaseIterable, Identifiable {
    case action = "动作"
    case comedy = "喜剧"
    case drama = "剧情"
    case horror = "恐怖"
    var id: MovieCategory { self }
}
struct MoviePicker: View {
   @State private var selectedCategory: MovieCategory = .action
  var body: some View {
     Picker("电影类别", selection: $selectedCategory) {
        ForEach(MovieCategory.allCases) { category in
             Text(category.rawValue).tag(category)
       }
     }
   }
}样式
SwiftUI 提供了多种内置的 Picker 样式,以改变 Picker 的外观和行为。以下是一些主要的 Picker 样式及其使用示例:
- DefaultPickerStyle:根据平台和环境自动调整样式。这是默认的- Picker样式。
Picker("Label", selection: $selection) {
    ForEach(0..<options.count) {
        Text(self.options[$0])
    }
}- WheelPickerStyle:以旋转轮的形式展示选项。在 iOS 上,这种样式会显示一个滚动的选择器。
Picker("Label", selection: $selection) {
    ForEach(0..<options.count) {
        Text(self.options[$0])
    }
}
.pickerStyle(WheelPickerStyle())- SegmentedPickerStyle:将选项以分段控件的形式展示。这种样式会显示一个分段控制,用户可以在其中选择一个选项。
Picker("Label", selection: $selection) {
    ForEach(0..<options.count) {
        Text(self.options[$0])
    }
}
.pickerStyle(SegmentedPickerStyle())- InlinePickerStyle:在列表或表格中内联展示选项。这种样式会在- Form或- List中显示一个内联的选择器。
Form {
    Picker("Label", selection: $selection) {
        ForEach(0..<options.count) {
            Text(self.options[$0])
        }
    }
    .pickerStyle(InlinePickerStyle())
}- MenuPickerStyle:点击时以菜单的形式展示选项。这种样式会显示一个菜单,用户可以在其中选择一个选项。
Picker("Label", selection: $selection) {
    ForEach(0..<options.count) {
        Text(self.options[$0])
    }
}
.pickerStyle(MenuPickerStyle())- .navigationLink:在 iOS 16+ 中,点击后进入下一个页面。这种样式会显示一个导航链接,用户可以点击它来打开一个新的视图。
- .radioGrouped:仅在 macOS 中可用,以单选按钮组的形式展示选项。这种样式会显示一个单选按钮组,用户可以在其中选择一个选项。
ColorPicker
ColorPicker 是一个允许用户选择颜色的视图。以下是一个 ColorPicker 的使用示例:
import SwiftUI
struct ContentView: View {
    @State private var selectedColor = Color.white
    var body: some View {
        VStack {
            ColorPicker("选择一个颜色", selection: $selectedColor)
            Text("你选择的颜色")
                .foregroundColor(selectedColor)
        }
    }
}在这个示例中,我们创建了一个 ColorPicker 视图,用户可以通过这个视图选择一个颜色。我们使用 @State 属性包装器来创建一个可以绑定到 ColorPicker 的 selectedColor 状态。当用户选择一个新的颜色时,selectedColor 状态会自动更新,Text 视图的前景色也会相应地更新。
DatePicker
基本使用
struct ContentView: View {
    @State private var releaseDate: Date = Date()
    var body: some View {
        VStack(spacing: 30) {
            DatePicker("选择电影发布日期", selection: $releaseDate, displayedComponents: .date)
            Text("选择的发布日期: \(releaseDate, formatter: DateFormatter.dateMedium)")
        }
        .padding()
    }
}选择多个日期
在 iOS 16 中,您现在可以允许用户选择多个日期,MultiDatePicker 视图会显示一个日历,用户可以选择多个日期,可以设置选择范围。示例如下:
struct PMultiDatePicker: View {
    @Environment(\.calendar) var cal
    @State var dates: Set<DateComponents> = []
    var body: some View {
        MultiDatePicker("选择个日子", selection: $dates, in: Date.now...)
        Text(s)
    }
    var s: String {
        dates.compactMap { c in
            cal.date(from:c)?.formatted(date: .long, time: .omitted)
        }
        .formatted()
    }
}指定日期范围
指定日期的范围,例如只能选择当前日期之后的日期,示例如下:
DatePicker(
    "选择日期",
    selection: $selectedDate,
    in: Date()...,
    displayedComponents: [.date]
)
.datePickerStyle(WheelDatePickerStyle())
.labelsHidden()在这个示例中:
- selection: $selectedDate表示选定的日期和时间。
- in: Date()...表示可选日期的范围。在这个例子中,用户只能选择当前日期之后的日期。你也可以使用- ...Date()来限制用户只能选择当前日期之前的日期,或者使用- Date().addingTimeInterval(86400*7)来限制用户只能选择从当前日期开始的接下来一周内的日期。
- displayedComponents: [.date]表示- DatePicker应该显示哪些组件。在这个例子中,我们只显示日期组件。你也可以使用- .hourAndMinute来显示小时和分钟组件,或者同时显示日期和时间组件。
- .datePickerStyle(WheelDatePickerStyle())表示- DatePicker的样式。在这个例子中,我们使用滚轮样式。你也可以使用- GraphicalDatePickerStyle()来应用图形样式。
- .labelsHidden()表示隐藏- DatePicker的标签。
PhotoPicker
PhotoPicker 使用示例
import SwiftUI
import PhotosUI
struct ContentView: View {
    @State private var selectedItem: PhotosPickerItem?
    @State private var selectedPhotoData: Data?
    var body: some View {
        NavigationView {
            VStack {
                if let item = selectedItem, let data = selectedPhotoData, let image = UIImage(data: data) {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                } else {
                    Text("选择电影海报")
                }
            }
            .navigationTitle("电影海报")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    PhotosPicker(selection: $selectedItem, matching: .images) {
                        Label("选择照片", systemImage: "photo")
                    }
                    .tint(.indigo)
                    .controlSize(.extraLarge)
                    .buttonStyle(.borderedProminent)
                }
            }
            .onChange(of: selectedItem, { oldValue, newValue in
                Task {
                    if let data = try? await newValue?.loadTransferable(type: Data.self) {
                        selectedPhotoData = data
                    }
                }
            })
        }
    }
}限制选择媒体类型
我们可以使用 matching 参数来过滤 PhotosPicker 中显示的媒体类型。这个参数接受一个 PHAssetMediaType 枚举值,可以是 .images、.videos、.audio、.any 等。
例如,如果我们只想显示图片,可以这样设置:
PhotosPicker(selection: $selectedItem, matching: .images) {
    Label("选择照片", systemImage: "photo")
}如果我们想同时显示图片和视频,可以使用 .any(of:) 方法:
PhotosPicker(selection: $selectedItem, matching: .any(of: [.images, .videos])) {
    Label("选择照片", systemImage: "photo")
}此外,我们还可以使用 .not(_:) 方法来排除某种类型的媒体。例如,如果我们想显示所有的图片,但是不包括 Live Photo,可以这样设置:
PhotosPicker(selection: $selectedItem, matching: .any(of: [.images, .not(.livePhotos)])) {
    Label("选择照片", systemImage: "photo")
}这些设置可以让我们更精确地控制 PhotosPicker 中显示的媒体类型。
选择多张图片
以下示例演示了如何使用 PhotosPicker 选择多张图片,并将它们显示在一个 LazyVGrid 中:
import SwiftUI
import PhotosUI
struct ContentView: View {
    @State private var selectedItems: [PhotosPickerItem] = [PhotosPickerItem]()
    @State private var selectedPhotosData: [Data] = [Data]()
    var body: some View {
        NavigationStack {
            ScrollView {
                LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
                    ForEach(selectedPhotosData, id: \.self) { photoData in
                        if let image = UIImage(data: photoData) {
                            Image(uiImage: image)
                                .resizable()
                                .scaledToFit()
                                .cornerRadius(10.0)
                                .padding(.horizontal)
                        }
                    }
                }
            }
            .navigationTitle("书籍")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    PhotosPicker(selection: $selectedItems, maxSelectionCount: 5, matching: .images) {
                        Image(systemName: "book.fill")
                            .foregroundColor(.brown)
                    }
                    .onChange(of: selectedItems, { oldValue, newValue in
                        for newItem in newValue {
                            Task {
                                if let data = try? await newItem.loadTransferable(type: Data.self) {
                                    selectedPhotosData.append(data)
                                }
                            }
                        }
                    })
                }
            }
        }
    }
}以上示例中,我们使用了 PhotosPicker 的 maxSelectionCount 参数来限制用户最多只能选择 5 张图片。当用户选择图片后,我们将图片数据保存在 selectedPhotosData 数组中,并在 LazyVGrid 中显示这些图片。
字体Picker
这段代码实现了一个字体选择器的功能,用户可以在其中选择和查看自己喜欢的字体。
struct ContentView: View {
    @State private var fontFamily: String = ""
    var body: some View {
        VStack {
            Text("选择字体:")
            FontPicker(fontFamily: $fontFamily)
                .equatable()
        }
    }
}
struct FontPicker: View, Equatable {
    @Binding var fontFamily: String
    var body: some View {
        VStack {
            Text("\(fontFamily)")
                .font(.custom(fontFamily, size: 20))
            Picker("", selection: $fontFamily) {
                ForEach(NSFontManager.shared.availableFontFamilies, id: \.self) { family in
                    Text(family)
                        .tag(family)
                }
            }
            Spacer()
        }
        .padding()
    }
    static func == (l: FontPicker, r: FontPicker) -> Bool {
        l.fontFamily == r.fontFamily
    }
}WheelPicker
本示例是一个可折叠的滚轮选择器 CollapsibleWheelPicker。这个选择器允许用户从一组书籍中选择一本。
struct ContentView: View {
  @State private var selection = 0
  let items = ["Book 1", "Book 2", "Book 3", "Book 4", "Book 5"]
  var body: some View {
    NavigationStack {
      Form {
        CollapsibleWheelPicker(selection: $selection) {
          ForEach(items, id: \.self) { item in
            Text("\(item)")
          }
        } label: {
          Text("Books")
          Spacer()
          Text("\(items[selection])")
        }
      }
    }
  }
}
struct CollapsibleWheelPicker<SelectionValue, Content, Label>: View where SelectionValue: Hashable, Content: View, Label: View {
    @Binding var selection: SelectionValue
    @ViewBuilder let content: () -> Content
    @ViewBuilder let label: () -> Label
    var body: some View {
        CollapsibleView(label: label) {
            Picker(selection: $selection, content: content) {
                EmptyView()
            }
            .pickerStyle(.wheel)
        }
    }
}
struct CollapsibleView<Label, Content>: View where Label: View, Content: View {
  @State private var isSecondaryViewVisible = false
  @ViewBuilder let label: () -> Label
  @ViewBuilder let content: () -> Content
  var body: some View {
    Group {
      Button(action: { isSecondaryViewVisible.toggle() }, label: label)
        .buttonStyle(.plain)
      if isSecondaryViewVisible {
        content()
      }
    }
  }
}
在 ContentView 中,我们创建了一个 CollapsibleWheelPicker 视图。这个视图包含一个滚轮样式的选择器,用户可以从中选择一本书。选择的书籍会绑定到 selection 变量。
CollapsibleWheelPicker 视图是一个可折叠的滚轮选择器,它接受一个绑定的选择变量、一个内容视图和一个标签视图。内容视图是一个 Picker 视图,用于显示可供选择的书籍。标签视图是一个 Text 视图,显示当前选择的书籍。
Toggle

示例
使用示例如下
struct PlayToggleView: View {
    @State private var isEnable = false
    var body: some View {
        // 普通样式
        Toggle(isOn: $isEnable) {
            Text("\(isEnable ? "开了" : "关了")")
        }
        .padding()
        
        // 按钮样式
        Toggle(isOn: $isEnable) {
            Label("\(isEnable ? "打开了" : "关闭了")", systemImage: "cloud.moon")
        }
        .padding()
        .tint(.pink)
        .controlSize(.large)
        .toggleStyle(.button)
        
        // Switch 样式
        Toggle(isOn: $isEnable) {
            Text("\(isEnable ? "开了" : "关了")")
        }
        .toggleStyle(SwitchToggleStyle(tint: .orange))
        .padding()
        
        // 自定义样式
        Toggle(isOn: $isEnable) {
            Text(isEnable ? "录音中" : "已静音")
        }
        .toggleStyle(PCToggleStyle())
        
    }
}
// MARK: - 自定义样式
struct PCToggleStyle: ToggleStyle {
    func makeBody(configuration: Configuration) -> some View {
        return HStack {
            configuration.label
            Image(systemName: configuration.isOn ? "mic.square.fill" : "mic.slash.circle.fill")
                .renderingMode(.original)
                .resizable()
                .frame(width: 30, height: 30)
                .onTapGesture {
                    configuration.isOn.toggle()
                }
        }
    }
}样式
Toggle 可以设置 toggleStyle,可以自定义样式。
下表是不同平台支持的样式
- DefaultToggleStyle:iOS 表现的是 Switch,macOS 是 Checkbox
- SwitchToggleStyle:iOS 和 macOS 都支持
- CheckboxToggleStyle:只支持 macOS
纯图像的 Toggle
struct ContentView: View {
    @State private var isMuted = false
    var body: some View {
        Toggle(isOn: $isMuted) {
            Image(systemName: isMuted ? "speaker.slash.fill" : "speaker.fill")
                .font(.system(size: 50))
        }
        .tint(.red)
        .toggleStyle(.button)
        .clipShape(Circle())
    }
}自定义 ToggleStyle
做一个自定义的切换按钮 OfflineModeToggleStyle。这个切换按钮允许用户控制是否开启离线模式。代码如下:
struct ContentView: View {
    @State private var isOfflineMode = false
    var body: some View {
        Toggle(isOn: $isOfflineMode) {
            Text("Offline Mode")
        }
        .toggleStyle(OfflineModeToggleStyle(systemImage: isOfflineMode ? "wifi.slash" : "wifi", activeColor: .blue))
    }
}
struct OfflineModeToggleStyle: ToggleStyle {
    var systemImage: String
    var activeColor: Color
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.label
            Spacer()
            RoundedRectangle(cornerRadius: 16)
                .fill(configuration.isOn ? activeColor : Color(.systemGray5))
                .overlay {
                    Circle()
                        .fill(.white)
                        .padding(2)
                        .overlay {
                            Image(systemName: systemImage)
                                .foregroundColor(configuration.isOn ? activeColor : Color(.systemGray5))
                        }
                        .offset(x: configuration.isOn ? 8 : -8)
                }
                .frame(width: 50, height: 32)
                .onTapGesture {
                    withAnimation(.spring()) {
                        configuration.isOn.toggle()
                    }
                }
        }
    }
}以上代码中,我们定义了一个 OfflineModeToggleStyle,它接受两个参数:systemImage 和 activeColor。systemImage 是一个字符串,表示图像的系统名称。activeColor 是一个颜色,表示激活状态的颜色。
动画化的 Toggle
以下是一个自定义的切换按钮 MuteToggleStyle。这个切换按钮允许用户控制是否开启静音模式。
struct ContentView: View {
    @State private var isMuted = false
    var body: some View {
        VStack {
            Toggle(isOn: $isMuted) {
                Text("Mute Mode")
                    .foregroundColor(isMuted ? .white : .black)
            }
            .toggleStyle(MuteToggleStyle())
            .padding()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}
struct MuteToggleStyle: ToggleStyle {
    var onImage = "speaker.slash.fill"
    var offImage = "speaker.2.fill"
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.label
            Spacer()
            RoundedRectangle(cornerRadius: 30)
                .fill(configuration.isOn ? Color(.systemGray6) : .yellow)
                .overlay {
                    Image(systemName: configuration.isOn ? onImage : offImage)
                        .resizable()
                        .scaledToFit()
                        .clipShape(Circle())
                        .padding(5)
                        .rotationEffect(.degrees(configuration.isOn ? 0 : 180))
                        .offset(x: configuration.isOn ? 10 : -10)
                }
                .frame(width: 50, height: 32)
                .onTapGesture {
                    withAnimation(.easeInOut(duration: 0.2)) {
                        configuration.isOn.toggle()
                    }
                }
        }
    }
}
extension ToggleStyle where Self == MuteToggleStyle {
    static var mute: MuteToggleStyle { .init() }
}以上代码中,我们定义了一个 MuteToggleStyle,它接受两个参数:onImage 和 offImage。onImage 是一个字符串,表示激活状态的图像的系统名称。offImage 是一个字符串,表示非激活状态的图像的系统名称。
两个标签的 Toggle
以下是一个自定义的切换按钮,它有两个标签。这个切换按钮允许用户控制是否开启静音模式。
Toggle(isOn: $mute) {
  Text("静音")
  Text("这将关闭所有声音")
}Slider
简单示例
struct PlaySliderView: View {
    @State var count: Double = 0
    var body: some View {
        Slider(value: $count, in: 0...100)
            .padding()
        Text("\(Int(count))")
    }
}以下代码演示了如何创建一个自定义的 Slider 控件,用于调整亮度。
struct ContentView: View {
    @State private var brightness: Double = 50
    @State private var isEditing: Bool = false
    var body: some View {
        VStack {
            Text("Brightness Control")
                .font(.title)
                .padding()
            BrightnessSlider(value: $brightness, range: 0...100, step: 5, isEditing: $isEditing)
            Text("Brightness: \(Int(brightness)), is changing: \(isEditing)")
                .font(.footnote)
                .padding()
        }
    }
}
struct BrightnessSlider: View {
    @Binding var value: Double
    var range: ClosedRange<Double>
    var step: Double
    @Binding var isEditing: Bool
    var body: some View {
        Slider(value: $value, in: range, step: step) {
            Label("亮度", systemImage: "light.max")
        } minimumValueLabel: {
            Text("\(Int(range.lowerBound))")
        } maximumValueLabel: {
            Text("\(Int(range.upperBound))")
        } onEditingChanged: {
            print($0)
        }
    }
}以上代码中,我们创建了一个 BrightnessSlider 控件,它是一个自定义的 Slider 控件,用于调整亮度。BrightnessSlider 接受一个 value 绑定,一个 range 范围,一个 step 步长,以及一个 isEditing 绑定。在 BrightnessSlider 中,我们使用 Slider 控件来显示亮度调整器。我们还使用 Label 来显示亮度调整器的标题,并使用 minimumValueLabel 和 maximumValueLabel 来显示亮度调整器的最小值和最大值。最后,我们使用 onEditingChanged 修饰符来监听亮度调整器的编辑状态。
Stepper
Stepper 控件允许用户通过点击按钮来增加或减少数值。
struct ContentView: View {
    @State private var count: Int = 2
    var body: some View {
        Stepper(value: $count, in: 2...20, step: 2) {
            Text("共\(count)")
        } onEditingChanged: { b in
            print(b)
        } // end Stepper
    }
}在 ContentView 中,我们定义了一个状态变量 count,并将其初始化为 2。然后,我们创建了一个 Stepper 视图,并将其绑定到 count 状态变量。
Stepper 视图的值范围为 2 到 20,步进值为 2,这意味着每次点击按钮,count 的值会增加或减少 2。我们还添加了一个标签,显示当前的 count 值。
我们还添加了 onEditingChanged 回调,当 Stepper 的值改变时,会打印出一个布尔值,表示 Stepper 是否正在被编辑。
