SwiftUIViews

控件和指示器(Controls and Indicators)

全面解析 SwiftUI 中的控件与指示器,涵盖按钮、开关、选择器、滑块、进度视图等所有交互组件的使用方法与最佳实践。

SwiftUI 提供了丰富的控件和指示器,用于处理用户交互和显示信息。这些组件针对不同平台和上下文进行了优化,能够帮助开发者快速构建直观、易用的用户界面。

概述

控件和指示器是 SwiftUI 中用于用户交互的核心组件集合。控件(Controls)用于接收用户输入和触发操作,而指示器(Indicators)用于向用户展示状态和进度信息。

主要分类:

  • 按钮类控件:触发操作和命令
  • 选择控件:从选项中进行选择
  • 数值调整控件:调整数值范围
  • 进度指示器:显示任务进度和状态
  • 分组控件:组织和管理相关内容
  • 链接控件:导航和分享

按钮类控件

Button

Button 是最基础的交互控件,用于触发操作。关于 Button 的详细内容,请参考专门的 Button 学习笔记。

基本用法:

Button("点击我") {
    print("按钮被点击")
}
.buttonStyle(.borderedProminent)

EditButton

EditButton 是一个特殊按钮,用于切换列表的编辑模式。

struct EditableListView: View {
    @State private var items = ["项目 1", "项目 2", "项目 3"]
    
    var body: some View {
        NavigationStack {
            List {
                ForEach(items, id: \.self) { item in
                    Text(item)
                }
                .onDelete { indexSet in
                    items.remove(atOffsets: indexSet)
                }
                .onMove { source, destination in
                    items.move(fromOffsets: source, toOffset: destination)
                }
            }
            .navigationTitle("可编辑列表")
            .toolbar {
                EditButton()  // 自动切换编辑模式
            }
        }
    }
}

特点:

  • 自动管理 editMode 环境值
  • 在编辑和完成状态之间切换
  • 仅在 iOS、iPadOS 和 Mac Catalyst 上可用

PasteButton

PasteButton 提供系统级的粘贴功能,可以从剪贴板读取特定类型的数据。

struct PasteButtonExample: View {
    @State private var pastedText = ""
    
    var body: some View {
        VStack(spacing: 20) {
            Text("粘贴的内容:\(pastedText)")
                .padding()
            
            PasteButton(payloadType: String.self) { strings in
                pastedText = strings.first ?? ""
            }
        }
    }
}

支持多种内容类型:

import UniformTypeIdentifiers

PasteButton(supportedContentTypes: [.plainText, .url]) { items in
    for item in items {
        item.loadItem(forTypeIdentifier: UTType.plainText.identifier) { data, error in
            if let data = data as? Data,
               let text = String(data: data, encoding: .utf8) {
                print("粘贴的文本:\(text)")
            }
        }
    }
}

RenameButton

RenameButton 触发标准的重命名操作,配合 .renameAction() 修饰符使用。

struct RenameButtonExample: View {
    @State private var itemName = "我的文档"
    @FocusState private var isFocused: Bool
    
    var body: some View {
        NavigationStack {
            VStack {
                TextField("名称", text: $itemName)
                    .focused($isFocused)
                    .textFieldStyle(.roundedBorder)
                    .padding()
            }
            .navigationTitle("文档")
            .toolbar {
                RenameButton()
            }
            .renameAction {
                isFocused = true
            }
        }
    }
}

链接控件

Link 用于导航到 URL,会在系统默认浏览器中打开链接。

// 基本链接
Link("访问 Apple", destination: URL(string: "https://www.apple.com")!)

// 自定义样式
Link(destination: URL(string: "https://developer.apple.com")!) {
    HStack {
        Image(systemName: "safari")
        Text("开发者文档")
    }
    .foregroundStyle(.blue)
}

ShareLink 提供系统级的分享功能,可以分享文本、图片、URL 等内容。

struct ShareLinkExample: View {
    let shareText = "查看这个很棒的应用!"
    let shareURL = URL(string: "https://example.com")!
    
    var body: some View {
        VStack(spacing: 20) {
            // 分享文本
            ShareLink(item: shareText)
            
            // 分享 URL
            ShareLink(item: shareURL) {
                Label("分享链接", systemImage: "square.and.arrow.up")
            }
            
            // 分享带预览
            ShareLink(
                item: shareURL,
                subject: Text("精彩内容"),
                message: Text("不要错过这个!")
            )
        }
    }
}

分享图片:

ShareLink(
    item: Image("photo"),
    preview: SharePreview(
        "美丽的风景",
        image: Image("photo")
    )
)

选择控件

Toggle

Toggle 是一个开关控件,用于在开启和关闭状态之间切换。

struct ToggleExample: View {
    @State private var isOn = false
    @State private var notifications = true
    @State private var darkMode = false
    
    var body: some View {
        Form {
            Section("设置") {
                Toggle("启用功能", isOn: $isOn)
                
                Toggle(isOn: $notifications) {
                    HStack {
                        Image(systemName: "bell.fill")
                        Text("推送通知")
                    }
                }
                
                Toggle("深色模式", systemImage: "moon.fill", isOn: $darkMode)
            }
        }
    }
}

自定义样式:

Toggle("自动保存", isOn: $isOn)
    .toggleStyle(.switch)  // 开关样式(默认)
    .tint(.purple)

Toggle("同意条款", isOn: $agreed)
    .toggleStyle(.button)  // 按钮样式

Picker

Picker 用于从一组互斥的选项中进行选择。

struct PickerExample: View {
    @State private var selectedFruit = "苹果"
    @State private var selectedColor = Color.red
    
    let fruits = ["苹果", "香蕉", "橙子", "葡萄"]
    
    var body: some View {
        Form {
            // 基本 Picker
            Picker("选择水果", selection: $selectedFruit) {
                ForEach(fruits, id: \.self) { fruit in
                    Text(fruit).tag(fruit)
                }
            }
            
            // 分段控件样式
            Picker("颜色", selection: $selectedColor) {
                Text("红色").tag(Color.red)
                Text("绿色").tag(Color.green)
                Text("蓝色").tag(Color.blue)
            }
            .pickerStyle(.segmented)
        }
    }
}

不同的 Picker 样式:

// 菜单样式
Picker("选项", selection: $selection) {
    // ...
}
.pickerStyle(.menu)

// 轮盘样式(仅 iOS)
Picker("选项", selection: $selection) {
    // ...
}
.pickerStyle(.wheel)

// 内联样式
Picker("选项", selection: $selection) {
    // ...
}
.pickerStyle(.inline)

// 导航样式
Picker("选项", selection: $selection) {
    // ...
}
.pickerStyle(.navigationLink)

DatePicker

DatePicker 用于选择日期和时间。

struct DatePickerExample: View {
    @State private var selectedDate = Date()
    @State private var birthDate = Date()
    @State private var meetingTime = Date()
    
    var body: some View {
        Form {
            // 完整日期和时间
            DatePicker("选择日期", selection: $selectedDate)
            
            // 仅日期
            DatePicker(
                "生日",
                selection: $birthDate,
                displayedComponents: .date
            )
            
            // 仅时间
            DatePicker(
                "会议时间",
                selection: $meetingTime,
                displayedComponents: .hourAndMinute
            )
            
            // 限制日期范围
            DatePicker(
                "预约日期",
                selection: $selectedDate,
                in: Date()...,  // 只能选择今天及以后
                displayedComponents: .date
            )
        }
    }
}

紧凑样式:

DatePicker("日期", selection: $date)
    .datePickerStyle(.compact)

DatePicker("日期", selection: $date)
    .datePickerStyle(.graphical)  // 图形化日历

DatePicker("日期", selection: $date)
    .datePickerStyle(.wheel)  // 轮盘样式

MultiDatePicker

MultiDatePicker 允许选择多个日期。

struct MultiDatePickerExample: View {
    @State private var selectedDates: Set<DateComponents> = []
    
    var body: some View {
        VStack {
            MultiDatePicker("选择日期", selection: $selectedDates)
            
            Text("已选择 \(selectedDates.count) 个日期")
                .padding()
            
            // 限制日期范围
            MultiDatePicker(
                "可用日期",
                selection: $selectedDates,
                in: Date()...
            )
        }
    }
}

ColorPicker

ColorPicker 用于选择颜色。

struct ColorPickerExample: View {
    @State private var selectedColor = Color.blue
    @State private var backgroundColor = Color.white
    
    var body: some View {
        VStack(spacing: 20) {
            ColorPicker("选择颜色", selection: $selectedColor)
            
            // 支持透明度
            ColorPicker("背景色", selection: $backgroundColor, supportsOpacity: true)
            
            Rectangle()
                .fill(selectedColor)
                .frame(height: 100)
                .cornerRadius(10)
        }
        .padding()
    }
}

数值调整控件

Slider

Slider 用于在一个范围内选择数值。

struct SliderExample: View {
    @State private var volume: Double = 50
    @State private var brightness: Double = 0.5
    @State private var temperature: Double = 20
    
    var body: some View {
        Form {
            // 基本滑块
            Slider(value: $volume, in: 0...100)
            Text("音量:\(Int(volume))")
            
            // 带标签
            Slider(value: $brightness, in: 0...1) {
                Text("亮度")
            }
            
            // 带最小/最大值标签
            Slider(
                value: $temperature,
                in: 0...40,
                step: 0.5
            ) {
                Text("温度")
            } minimumValueLabel: {
                Text("0°")
            } maximumValueLabel: {
                Text("40°")
            }
            
            Text("当前温度:\(temperature, specifier: "%.1f")°C")
        }
    }
}

监听编辑状态:

Slider(
    value: $value,
    in: 0...100,
    onEditingChanged: { editing in
        if editing {
            print("开始拖动")
        } else {
            print("结束拖动,最终值:\(value)")
        }
    }
)

Stepper

Stepper 用于通过增减按钮调整数值。

struct StepperExample: View {
    @State private var quantity = 1
    @State private var age = 18
    @State private var price = 9.99
    
    var body: some View {
        Form {
            // 基本步进器
            Stepper("数量:\(quantity)", value: $quantity)
            
            // 指定范围和步长
            Stepper(
                "年龄:\(age)",
                value: $age,
                in: 0...120,
                step: 1
            )
            
            // 浮点数步进器
            Stepper(
                value: $price,
                in: 0...100,
                step: 0.5
            ) {
                Text("价格:$\(price, specifier: "%.2f")")
            }
            
            // 自定义增减逻辑
            Stepper("自定义") {
                quantity += 5
            } onDecrement: {
                quantity = max(0, quantity - 5)
            }
        }
    }
}

进度指示器

ProgressView

ProgressView 用于显示任务的进度。

struct ProgressViewExample: View {
    @State private var progress = 0.0
    @State private var isLoading = false
    
    var body: some View {
        VStack(spacing: 30) {
            // 不确定进度(旋转指示器)
            ProgressView()
            
            ProgressView("加载中...")
            
            // 确定进度
            ProgressView(value: progress, total: 100)
            
            // 带标签和当前值
            ProgressView(value: progress, total: 100) {
                Text("下载进度")
            } currentValueLabel: {
                Text("\(Int(progress))%")
            }
            
            Button("开始") {
                startProgress()
            }
        }
        .padding()
    }
    
    func startProgress() {
        progress = 0
        Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
            progress += 1
            if progress >= 100 {
                timer.invalidate()
            }
        }
    }
}

自定义样式:

ProgressView(value: progress)
    .progressViewStyle(.linear)  // 线性样式
    .tint(.purple)

ProgressView()
    .progressViewStyle(.circular)  // 圆形样式

时间进度:

// 倒计时
ProgressView(
    timerInterval: Date()...Date().addingTimeInterval(60),
    countsDown: true
) {
    Text("倒计时")
}

Gauge

Gauge 用于在一个范围内显示数值,类似仪表盘。

struct GaugeExample: View {
    @State private var speed: Double = 60
    @State private var battery: Double = 75
    @State private var temperature: Double = 22
    
    var body: some View {
        VStack(spacing: 30) {
            // 基本仪表
            Gauge(value: speed, in: 0...200) {
                Text("速度")
            }
            
            // 带当前值标签
            Gauge(value: battery, in: 0...100) {
                Text("电量")
            } currentValueLabel: {
                Text("\(Int(battery))%")
            }
            
            // 带最小/最大值标签
            Gauge(value: temperature, in: 0...40) {
                Text("温度")
            } currentValueLabel: {
                Text("\(Int(temperature))°")
            } minimumValueLabel: {
                Text("0°")
            } maximumValueLabel: {
                Text("40°")
            }
        }
        .padding()
    }
}

不同的 Gauge 样式:

// 线性容量样式
Gauge(value: storage, in: 0...100) {
    Text("存储")
}
.gaugeStyle(.linearCapacity)

// 圆形样式(watchOS)
Gauge(value: heartRate, in: 0...200) {
    Text("心率")
}
.gaugeStyle(.circular)

// 访问样式
Gauge(value: progress, in: 0...100) {
    Text("进度")
}
.gaugeStyle(.accessoryCircular)

分组控件

GroupBox

GroupBox 用于在视觉上将相关内容分组。

struct GroupBoxExample: View {
    var body: some View {
        VStack(spacing: 20) {
            // 无标签分组
            GroupBox {
                Text("这是一组相关内容")
                Text("它们被视觉上分组在一起")
            }
            
            // 带标签分组
            GroupBox("用户信息") {
                VStack(alignment: .leading, spacing: 10) {
                    Text("姓名:张三")
                    Text("邮箱:zhangsan@example.com")
                    Text("电话:123-456-7890")
                }
            }
            
            // 自定义标签
            GroupBox {
                VStack(alignment: .leading, spacing: 10) {
                    Text("设置项 1")
                    Text("设置项 2")
                    Text("设置项 3")
                }
            } label: {
                Label("高级设置", systemImage: "gear")
                    .font(.headline)
            }
        }
        .padding()
    }
}

ControlGroup

ControlGroup 将语义相关的控件组合在一起,系统会根据上下文自动选择合适的显示方式。

struct ControlGroupExample: View {
    @State private var isBold = false
    @State private var isItalic = false
    @State private var isUnderline = false
    
    var body: some View {
        VStack(spacing: 20) {
            // 文本格式控件组
            ControlGroup {
                Button {
                    isBold.toggle()
                } label: {
                    Image(systemName: "bold")
                }
                
                Button {
                    isItalic.toggle()
                } label: {
                    Image(systemName: "italic")
                }
                
                Button {
                    isUnderline.toggle()
                } label: {
                    Image(systemName: "underline")
                }
            }
            
            // 带标签的控件组
            ControlGroup("对齐方式") {
                Button { } label: {
                    Image(systemName: "text.alignleft")
                }
                Button { } label: {
                    Image(systemName: "text.aligncenter")
                }
                Button { } label: {
                    Image(systemName: "text.alignright")
                }
            }
        }
        .padding()
    }
}

DisclosureGroup

DisclosureGroup 创建可折叠的内容区域。

struct DisclosureGroupExample: View {
    @State private var isExpanded = false
    @State private var showDetails = true
    
    var body: some View {
        VStack(spacing: 20) {
            // 基本折叠组
            DisclosureGroup("更多信息") {
                Text("这是隐藏的详细内容")
                Text("点击标题可以展开或折叠")
            }
            
            // 控制展开状态
            DisclosureGroup(
                "详细设置",
                isExpanded: $showDetails
            ) {
                Toggle("选项 1", isOn: .constant(true))
                Toggle("选项 2", isOn: .constant(false))
                Toggle("选项 3", isOn: .constant(true))
            }
            
            Button("切换展开") {
                showDetails.toggle()
            }
            
            // 嵌套折叠组
            DisclosureGroup("高级选项") {
                DisclosureGroup("网络设置") {
                    Text("WiFi 设置")
                    Text("蓝牙设置")
                }
                
                DisclosureGroup("隐私设置") {
                    Text("位置服务")
                    Text("相机权限")
                }
            }
        }
        .padding()
    }
}

其他控件

LabeledContent

LabeledContent 用于将标签和值配对显示。

struct LabeledContentExample: View {
    var body: some View {
        Form {
            Section("用户信息") {
                LabeledContent("姓名", value: "张三")
                LabeledContent("年龄", value: "28")
                LabeledContent("邮箱", value: "zhangsan@example.com")
            }
            
            Section("统计数据") {
                LabeledContent("总计") {
                    Text("¥1,234.56")
                        .foregroundStyle(.green)
                        .bold()
                }
                
                LabeledContent {
                    HStack {
                        Image(systemName: "star.fill")
                        Text("4.8")
                    }
                    .foregroundStyle(.orange)
                } label: {
                    Text("评分")
                }
            }
        }
    }
}

格式化数值:

let price = 99.99
let date = Date()

LabeledContent("价格", value: price, format: .currency(code: "USD"))
LabeledContent("日期", value: date, format: .dateTime)

触觉反馈

使用 sensoryFeedback 为控件添加触觉反馈。

struct SensoryFeedbackExample: View {
    @State private var isLiked = false
    @State private var count = 0
    
    var body: some View {
        VStack(spacing: 30) {
            Button {
                isLiked.toggle()
            } label: {
                Image(systemName: isLiked ? "heart.fill" : "heart")
                    .font(.largeTitle)
                    .foregroundStyle(isLiked ? .red : .gray)
            }
            .sensoryFeedback(.impact, trigger: isLiked)
            
            Button("增加") {
                count += 1
            }
            .sensoryFeedback(.increase, trigger: count)
            
            Button("成功操作") {
                // 执行操作
            }
            .sensoryFeedback(.success, trigger: count)
        }
    }
}

可用的反馈类型:

.sensoryFeedback(.success, trigger: value)    // 成功
.sensoryFeedback(.warning, trigger: value)    // 警告
.sensoryFeedback(.error, trigger: value)      // 错误
.sensoryFeedback(.selection, trigger: value)  // 选择
.sensoryFeedback(.increase, trigger: value)   // 增加
.sensoryFeedback(.decrease, trigger: value)   // 减少
.sensoryFeedback(.impact, trigger: value)     // 冲击

控件配置

控件尺寸

使用 controlSize 统一调整控件大小。

struct ControlSizeExample: View {
    @State private var value = 50.0
    
    var body: some View {
        VStack(spacing: 30) {
            VStack {
                Text("迷你尺寸")
                Slider(value: $value, in: 0...100)
                Button("按钮") { }
                    .buttonStyle(.bordered)
            }
            .controlSize(.mini)
            
            VStack {
                Text("小尺寸")
                Slider(value: $value, in: 0...100)
                Button("按钮") { }
                    .buttonStyle(.bordered)
            }
            .controlSize(.small)
            
            VStack {
                Text("常规尺寸")
                Slider(value: $value, in: 0...100)
                Button("按钮") { }
                    .buttonStyle(.bordered)
            }
            .controlSize(.regular)
            
            VStack {
                Text("大尺寸")
                Slider(value: $value, in: 0...100)
                Button("按钮") { }
                    .buttonStyle(.bordered)
            }
            .controlSize(.large)
        }
        .padding()
    }
}

控件样式定制

大多数控件都支持通过样式协议进行自定义:

// Toggle 样式
struct CustomToggleStyle: ToggleStyle {
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.label
            Spacer()
            RoundedRectangle(cornerRadius: 16)
                .fill(configuration.isOn ? Color.green : Color.gray)
                .frame(width: 50, height: 30)
                .overlay {
                    Circle()
                        .fill(.white)
                        .padding(3)
                        .offset(x: configuration.isOn ? 10 : -10)
                }
                .onTapGesture {
                    withAnimation(.spring()) {
                        configuration.isOn.toggle()
                    }
                }
        }
    }
}

// 使用
Toggle("自定义开关", isOn: $isOn)
    .toggleStyle(CustomToggleStyle())

实战示例

设置页面

struct SettingsView: View {
    @State private var notifications = true
    @State private var soundEnabled = true
    @State private var volume: Double = 70
    @State private var theme = "自动"
    @State private var fontSize: Double = 16
    
    let themes = ["浅色", "深色", "自动"]
    
    var body: some View {
        NavigationStack {
            Form {
                Section("通知") {
                    Toggle("推送通知", isOn: $notifications)
                    Toggle("声音", isOn: $soundEnabled)
                    
                    if soundEnabled {
                        VStack(alignment: .leading) {
                            Text("音量")
                            Slider(value: $volume, in: 0...100)
                            Text("\(Int(volume))%")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }
                    }
                }
                
                Section("外观") {
                    Picker("主题", selection: $theme) {
                        ForEach(themes, id: \.self) { theme in
                            Text(theme).tag(theme)
                        }
                    }
                    
                    Stepper(
                        "字体大小:\(Int(fontSize))",
                        value: $fontSize,
                        in: 12...24,
                        step: 2
                    )
                }
                
                Section("关于") {
                    LabeledContent("版本", value: "1.0.0")
                    LabeledContent("构建号", value: "100")
                }
            }
            .navigationTitle("设置")
        }
    }
}

表单验证

struct FormValidationView: View {
    @State private var name = ""
    @State private var email = ""
    @State private var age = 18
    @State private var agreedToTerms = false
    @State private var selectedCountry = "中国"
    @State private var birthDate = Date()
    
    let countries = ["中国", "美国", "英国", "日本"]
    
    var isFormValid: Bool {
        !name.isEmpty &&
        !email.isEmpty &&
        email.contains("@") &&
        agreedToTerms
    }
    
    var body: some View {
        NavigationStack {
            Form {
                Section("个人信息") {
                    TextField("姓名", text: $name)
                    TextField("邮箱", text: $email)
                        .keyboardType(.emailAddress)
                        .textInputAutocapitalization(.never)
                    
                    Stepper("年龄:\(age)", value: $age, in: 1...120)
                    
                    DatePicker(
                        "生日",
                        selection: $birthDate,
                        displayedComponents: .date
                    )
                    
                    Picker("国家", selection: $selectedCountry) {
                        ForEach(countries, id: \.self) { country in
                            Text(country).tag(country)
                        }
                    }
                }
                
                Section {
                    Toggle("我同意服务条款", isOn: $agreedToTerms)
                }
                
                Section {
                    Button("提交") {
                        submitForm()
                    }
                    .frame(maxWidth: .infinity)
                    .disabled(!isFormValid)
                }
            }
            .navigationTitle("注册")
        }
    }
    
    func submitForm() {
        print("表单已提交")
    }
}

下载管理器

struct DownloadManagerView: View {
    @State private var downloads: [Download] = [
        Download(name: "文件 1.pdf", progress: 0.3),
        Download(name: "图片.jpg", progress: 0.7),
        Download(name: "视频.mp4", progress: 0.5)
    ]
    
    var body: some View {
        NavigationStack {
            List {
                ForEach($downloads) { $download in
                    VStack(alignment: .leading, spacing: 8) {
                        HStack {
                            Text(download.name)
                                .font(.headline)
                            Spacer()
                            Text("\(Int(download.progress * 100))%")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }
                        
                        ProgressView(value: download.progress)
                        
                        HStack {
                            if download.progress < 1.0 {
                                Button("暂停") {
                                    // 暂停下载
                                }
                                .buttonStyle(.bordered)
                                .controlSize(.small)
                            } else {
                                Label("完成", systemImage: "checkmark.circle.fill")
                                    .foregroundStyle(.green)
                            }
                            
                            Spacer()
                            
                            Button("取消") {
                                cancelDownload(download)
                            }
                            .buttonStyle(.bordered)
                            .controlSize(.small)
                            .tint(.red)
                        }
                    }
                    .padding(.vertical, 8)
                }
            }
            .navigationTitle("下载管理")
        }
    }
    
    func cancelDownload(_ download: Download) {
        downloads.removeAll { $0.id == download.id }
    }
}

struct Download: Identifiable {
    let id = UUID()
    var name: String
    var progress: Double
}

平台差异

不同平台上控件的表现有所差异:

控件iOS/iPadOSmacOSwatchOStvOS
Button触摸交互点击交互数字表冠遥控器
Toggle开关样式复选框开关样式不可用
Picker多种样式下拉菜单轮盘样式不可用
Slider触摸拖动鼠标拖动数字表冠不可用
DatePicker轮盘/日历日历弹窗不可用不可用
ColorPicker颜色选择器系统颜色面板不可用不可用

最佳实践

1. 选择合适的控件

// ✅ 推荐:二选一用 Toggle
Toggle("启用功能", isOn: $isEnabled)

// ❌ 不推荐:二选一用 Picker
Picker("功能", selection: $isEnabled) {
    Text("启用").tag(true)
    Text("禁用").tag(false)
}

2. 提供清晰的标签

// ✅ 推荐:描述性标签
Slider(value: $brightness, in: 0...1) {
    Text("屏幕亮度")
}

// ❌ 不推荐:无标签或模糊标签
Slider(value: $brightness, in: 0...1)

3. 合理使用控件尺寸

// ✅ 推荐:根据上下文调整尺寸
VStack {
    // 工具栏中的小按钮
    HStack {
        Button("编辑") { }
        Button("删除") { }
    }
    .controlSize(.small)
    
    // 主要操作使用大按钮
    Button("提交") { }
        .controlSize(.large)
}

4. 提供即时反馈

// ✅ 推荐:显示当前值
Stepper("数量:\(quantity)", value: $quantity)

Slider(value: $volume, in: 0...100)
Text("音量:\(Int(volume))%")

5. 考虑可访问性

Slider(value: $volume, in: 0...100)
    .accessibilityLabel("音量")
    .accessibilityValue("\(Int(volume))%")

Button(action: { }) {
    Image(systemName: "trash")
}
.accessibilityLabel("删除")
.accessibilityHint("删除当前项目")

总结

SwiftUI 的 Controls and Indicators 提供了完整的用户交互解决方案:

  • 按钮类控件:处理用户操作和命令
  • 选择控件:提供多种选择方式,适应不同场景
  • 数值调整控件:精确或快速调整数值
  • 进度指示器:清晰展示任务状态
  • 分组控件:组织和管理复杂界面
  • 配置选项:统一控制外观和行为

通过合理组合这些控件,可以构建出功能强大、体验优秀的用户界面。记住要根据具体场景选择合适的控件,提供清晰的标签和即时反馈,并始终考虑可访问性。

参考资源

在 GitHub 上编辑

上次更新于