Skip to the content.

SwiftUI 学习日志(3):数据绑定与状态管理

欢迎来到《SwiftUI 学习日志》的第 3 篇文章。在本篇文章中,我们将深入探讨 SwiftUI 中的数据绑定状态管理。数据绑定和状态管理是 SwiftUI 的核心概念,通过理解和掌握这些概念,您将能够构建出更加灵活和动态的用户界面。

1. SwiftUI 数据绑定简介

1.1 什么是数据绑定

数据绑定是指在视图数据模型之间建立一种双向链接,使得视图能够实时反映数据模型的变化,并且用户在视图上的操作也能直接更新数据模型。在 SwiftUI 中,数据绑定使得界面和数据保持同步,极大简化了开发过程。

1.2 数据绑定的优势

2. 数据绑定与状态管理

2.1 @State 属性包装器

@State 属性包装器用于在视图内部声明状态变量,当状态变量的值发生变化时,视图会自动刷新。

/// ### 2.1 @State 属性包装器
struct StateExampleView: View {
    @State private var counter = 0      // 定义计数状态变量
    
    var body: some View {
        VStack {
            Text("计数器: \(counter)")
                .font(.largeTitle)
            Button(action: {
                counter += 1            // 点击按钮计数状态变量 + 1
            }) {
                Text("增加")
                    .font(.title)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
    }
}

#Preview {
    StateExampleView()
}

@State 属性包装器效果

@State 的使用场景

2.2 @Binding 属性包装器

@Binding 属性包装器用于在多个视图之间共享状态,父视图将状态变量传递给子视图,子视图通过 @Binding 修改父视图的状态。

/// ### 2.2 @Binding 属性包装器
struct BindingExampleView: View {
    @State private var isOn = false         // 定义开关状态变量
    
    var body: some View {
        VStack {
            Text(isOn ? "开" : "关").font(.largeTitle)
            BindingToggleView(isOn: $isOn)  // $状态名 向子视图传递状态
        }
    }
}

/// 绑定开关视图
struct BindingToggleView: View {
    @Binding var isOn: Bool                 // 接收父视图传递的状态
    
    var body: some View {
        Toggle("开关", isOn: $isOn)          // 绑定状态到控件
            .padding()
    }
}

#Preview {
    BindingExampleView()
}

@Binding 属性包装器效果

@Binding 的使用场景

2.3 @ObservableObject 和 @StateObject 属性包装器

/// ### 2.3 @ObservableObject 和 @StateObject 属性包装器
/// 计时器模型
class TimerModel: ObservableObject {
    @Published var timer: Int = 0       // 定义计时器发布的属性
    
    func start() {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            self.timer += 1             // 每秒计数器加一
        }
    }
}

/// 计时器视图
struct TimerView: View {
    @StateObject private var model = TimerModel()   // 本地计时器状态对象
    
    var body: some View {
        VStack {
            Text("时间: \(model.timer)")
                .font(.largeTitle)
            Button(action: {
                model.start()           // 点击按钮时启动计时器
            }) {
                Text("开始计时")
                    .padding()
                    .background(Color.green)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
        }
    }
}

#Preview {
    TimerView()
}

@ObservedObject 和 @Published 属性包装器效果

@ObservableObject 和 @StateObject 的使用场景

2.4. @EnvironmentObject 属性包装器

@EnvironmentObject 属性包装器用于在应用程序的多个视图之间共享全局数据模型数据模型通过环境对象注入到视图中

/// ### 2.4. @EnvironmentObject 属性包装器
/// 全局计数器模型
class GlobalCounterModel: ObservableObject {
    @Published var counter = 0
}

/// 环境对象实例视图
struct EnvironmentObjectExampleView: View {
    @StateObject private var model = GlobalCounterModel()
    
    var body: some View {
        VStack {
            Text("计数器: \(model.counter)")
                .font(.largeTitle)
            Button(action: {
                model.counter += 1
            }) {
                Text("增加")
                    .font(.title)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            Divider()
            EnvironmentChildView()
                .environmentObject(model)
        }
    }
}

/// 环境子视图
struct EnvironmentChildView: View {
    @EnvironmentObject var model: GlobalCounterModel
    
    var body: some View {
        Text("环境对象计数器: \(model.counter)")
            .font(.title)
    }
}

#Preview {
    EnvironmentObjectExampleView()
}

@EnvironmentObject 属性包装器效果

2.4.1 @EnvironmentObject 的使用场景

2.4.2 @StateObject 和 @EnvironmentObject 对比

@StateObject
@EnvironmentObject

3. 综合案例:任务管理器

3.1 案例简介

在这个综合案例中,我们将创建一个简单的任务管理器,展示如何使用数据绑定和状态管理来添加、删除和标记任务完成状态。

3.2 实现步骤

3.3 代码示例

新建 TaskView.swift 并输入以下代码:

import SwiftUI

/// ## 3. 综合案例:任务管理器
/// 任务模型
class Task: Identifiable, ObservableObject {
    let id = UUID()                     // 唯一标识符
    @Published var title: String        // 任务标题
    @Published var isCompleted: Bool    // 是否完成
    
    init(title: String, isCompleted: Bool = false) {
        self.title = title
        self.isCompleted = isCompleted
    }
}

/// 任务管理器
class TaskManager: ObservableObject {
    @Published var tasks: [Task] = []   // 任务数组
    
    func addTask(title: String) {
        let newTask = Task(title: title)
        tasks.append(newTask)           // 添加新任务到任务数组
    }
    
    func removeTask(at offsets: IndexSet) {
        tasks.remove(atOffsets: offsets)// 从任务数组中移除任务
    }
}

/// 任务视图
struct TaskView: View {
    @StateObject private var taskManager = TaskManager()  // 任务管理器状态
    @State private var newTaskTitle = ""                  // 新任务标题状态
    
    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    TextField("输入任务", text: $newTaskTitle)  // 绑定状态到文本输入框
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    Button(action: {
                        if !newTaskTitle.isEmpty {
                            taskManager.addTask(title: newTaskTitle)  // 添加新任务
                            newTaskTitle = ""   // 清空文本输入框
                        }
                    }) {
                        Text("添加")
                            .padding()
                            .background(Color.blue)
                            .foregroundColor(.white)
                            .cornerRadius(10)
                    }
                }
                .padding()
                
                List {
                    ForEach(taskManager.tasks) { task in
                        TaskRow(task: task)     // 使用 TaskRow 组件来展示任务
                    }
                    .onDelete(perform: taskManager.removeTask)  // 支持删除任务
                }
                .navigationTitle("任务管理器")
            }
        }
    }
}

/// 任务行视图
struct TaskRow: View {
    @ObservedObject var task: Task              // 观察 Task 对象的变化
    
    var body: some View {
        HStack {
            Text(task.title)
            Spacer()
            if task.isCompleted {
                Image(systemName: "checkmark.circle.fill")
                    .foregroundColor(.green)
            } else {
                Image(systemName: "circle")
                    .foregroundColor(.gray)
            }
        }
        .contentShape(Rectangle())
        .onTapGesture {
            task.isCompleted.toggle()           // 切换任务完成状态
        }
    }
}

#Preview {
    TaskView()
}

任务管理器代码及预览效果

在这个综合案例中,我们创建了一个简单的任务管理器,展示了如何使用 @State@Binding@ObservedObject@StateObject 来管理和传递状态。通过这些技术,我们可以轻松地实现任务的添加、删除和状态更新。

4. 结语

在这篇文章中,我们深入探讨了 SwiftUI 的数据绑定状态管理,包括 @State@Binding@ObservedObject@Published@EnvironmentObject 等属性包装器。通过综合案例,我们展示了如何在多个视图之间共享数据和状态。希望你对 SwiftUI 的数据绑定和状态管理有了更深入的理解。下一篇文章将进一步探讨 SwiftUI 的动画和手势,敬请期待。