🟡 第 2 周:数据绑定与状态管理

46 阅读1分钟

📖 学习内容

SwiftUI 采用 数据驱动 UI,主要使用 @State, @Binding, @ObservedObject, @EnvironmentObject 来管理数据状态。

1️⃣ @State(管理本地状态)

@State 用于组件内的小范围状态管理,比如计数器:

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: (count)")
            Button("Increase") {
                count += 1
            }
        }
    }
}
  • @State 修饰的变量只适用于当前 View,每次修改都会触发 UI 更新。

2️⃣ @Binding(父子组件数据传递)

用于在 父子组件之间共享状态,如 Toggle 控制开关:

struct ParentView: View {
    @State private var isOn = false

    var body: some View {
        ToggleView(isOn: $isOn)
    }
}

struct ToggleView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("开关", isOn: $isOn)
    }
}
  • $isOn 传递 @State 变量,使子组件能修改父组件的值。

3️⃣ @ObservedObject & @StateObject(可观察对象)

适用于 多个视图共享数据

class TaskManager: ObservableObject {
    @Published var tasks = ["学习 SwiftUI", "写博客"]
}

struct TaskListView: View {
    @StateObject private var manager = TaskManager()

    var body: some View {
        List(manager.tasks, id: .self) { task in
            Text(task)
        }
    }
}
  • @Published 让 tasks 变化时 UI 自动更新。

  • @StateObject 在 视图创建时初始化对象

4️⃣ @EnvironmentObject(全局状态)

适用于 多个视图共享数据,如 UserSettings:

class UserSettings: ObservableObject {
    @Published var username = "SwiftUI 学习者"
}

struct ContentView: View {
    @StateObject private var settings = UserSettings()

    var body: some View {
        UserProfileView().environmentObject(settings)
    }
}

struct UserProfileView: View {
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        Text("用户名: (settings.username)")
    }
}
  • @EnvironmentObject 让 settings 适用于整个 app。

5️⃣ List 和 ForEach(列表视图)

SwiftUI 的 List 适用于创建可滚动的列表:

struct TodoListView: View {
    let tasks = ["买菜", "写代码", "运动"]

    var body: some View {
        List(tasks, id: .self) { task in
            Text(task)
        }
    }
}
  • List 会自动处理滚动和复用,比 VStack 更高效。

6️⃣ NavigationStack(页面跳转)

用于 在 SwiftUI 里导航

struct HomeView: View {
    var body: some View {
        NavigationStack {
            NavigationLink("去详情页", destination: DetailView())
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("详情页")
    }
}
  • NavigationLink 允许用户点击后跳转。

💻 练习项目

To-Do List 应用****

  • List 显示任务,@State 记录完成状态

  • TextField 输入新任务

新闻列表 UI****

  • List + NavigationStack 实现新闻列表
  • 点击新闻后进入 详情页****
  • 练习 LazyVStack 实现懒加载

✅练习 1:To-Do List 应用

实现一个可添加、标记完成状态的代办事项列表。

import SwiftUI

struct TodoItem: Identifiable {
    let id = UUID()
    var title: String
    var isCompleted: Bool = false
}

class TodoViewModel: ObservableObject {
    @Published var todos: [TodoItem] = []
    
    func addTodo(_ title: String) {
        let newTodo = TodoItem(title: title)
        todos.append(newTodo)
    }

    func toggleTodo(_ todo: TodoItem) {
        if let index = todos.firstIndex(where: { $0.id == todo.id }) {
            todos[index].isCompleted.toggle()
        }
    }

    func deleteTodo(at offsets: IndexSet) {
        todos.remove(atOffsets: offsets)
    }
}

struct TodoListView: View {
    @StateObject private var viewModel = TodoViewModel()
    @State private var newTodo = ""

    var body: some View {
        NavigationStack {
            VStack {
                HStack {
                    TextField("输入新任务", text: $newTodo)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    Button("添加") {
                        guard !newTodo.isEmpty else { return }
                        viewModel.addTodo(newTodo)
                        newTodo = ""
                    }
                    .buttonStyle(.borderedProminent)
                }
                .padding()

                List {
                    ForEach(viewModel.todos) { todo in
                        HStack {
                            Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
                                .foregroundColor(todo.isCompleted ? .green : .gray)
                            Text(todo.title)
                                .strikethrough(todo.isCompleted)
                                .foregroundColor(todo.isCompleted ? .gray : .primary)
                        }
                        .onTapGesture {
                            viewModel.toggleTodo(todo)
                        }
                    }
                    .onDelete(perform: viewModel.deleteTodo)
                }
            }
            .navigationTitle("待办事项")
        }
    }
}

#Preview {
    TodoListView()
}

✅练习 2:新闻列表 UI(含导航跳转)

import SwiftUI

struct NewsArticle: Identifiable {
    let id = UUID()
    let title: String
    let content: String
}

struct NewsListView: View {
    let articles: [NewsArticle] = [
        NewsArticle(title: "SwiftUI 入门", content: "这是关于 SwiftUI 的入门教程..."),
        NewsArticle(title: "iOS 17 新特性", content: "探索 iOS 17 中的新功能..."),
        NewsArticle(title: "Apple Vision Pro 上手", content: "Vision Pro 正在改变一切...")
    ]

    var body: some View {
        NavigationStack {
            List(articles) { article in
                NavigationLink(destination: NewsDetailView(article: article)) {
                    Text(article.title)
                        .font(.headline)
                        .padding(.vertical, 4)
                }
            }
            .navigationTitle("新闻列表")
        }
    }
}

struct NewsDetailView: View {
    let article: NewsArticle

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 12) {
                Text(article.title)
                    .font(.title)
                    .bold()
                Text(article.content)
                    .font(.body)
            }
            .padding()
        }
        .navigationTitle("详情")
    }
}

#Preview {
    NewsListView()
}

📌 这两个练习会帮助我们掌握 SwiftUI 中的状态绑定、列表渲染、以及基本导航逻辑。