iOS App 从 0 到 1

实战:做一个待办 App

把前面章节串成一个可运行的小产品。

1目标产品

一个本地待办:列表、添加、滑动删除、完成切换、杀进程再开仍在。

建工程 → 模型+Store → List UI → 持久化 → 真机验收

2模型与 Store

struct TodoItem: Identifiable, Codable, Equatable {
  var id = UUID()
  var title: String
  var isDone: Bool = false
}

@Observable
final class TodoStore {
  var items: [TodoItem] = []
  private let fileName = "todos.json"

  private var url: URL {
    FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
      .appendingPathComponent(fileName)
  }

  func load() {
    guard let data = try? Data(contentsOf: url),
          let decoded = try? JSONDecoder().decode([TodoItem].self, from: data)
    else { return }
    items = decoded
  }

  func save() {
    guard let data = try? JSONEncoder().encode(items) else { return }
    try? data.write(to: url, options: .atomic)
  }

  func add(_ title: String) {
    let t = title.trimmingCharacters(in: .whitespacesAndNewlines)
    guard !t.isEmpty else { return }
    items.insert(TodoItem(title: t), at: 0)
    save()
  }

  func toggle(_ item: TodoItem) {
    guard let i = items.firstIndex(of: item) else { return }
    items[i].isDone.toggle()
    save()
  }

  func remove(at offsets: IndexSet) {
    items.remove(atOffsets: offsets)
    save()
  }
}

3界面与入口

struct TodoHomeView: View {
  @State private var store = TodoStore()
  @State private var draft = ""

  var body: some View {
    NavigationStack {
      List {
        ForEach(store.items) { item in
          HStack {
            Image(systemName: item.isDone ? "checkmark.circle.fill" : "circle")
              .onTapGesture { store.toggle(item) }
            Text(item.title)
              .strikethrough(item.isDone)
          }
        }
        .onDelete(perform: store.remove)
      }
      .navigationTitle("待办")
      .toolbar {
        ToolbarItem(placement: .bottomBar) {
          HStack {
            TextField("新任务", text: $draft)
            Button("添加") {
              store.add(draft)
              draft = ""
            }
          }
        }
      }
      .onAppear { store.load() }
    }
  }
}

@main
struct TodoDemoApp: App {
  var body: some Scene {
    WindowGroup { TodoHomeView() }
  }
}

4验收清单(真实标准)

功能

增、删、勾选完成都 OK。

持久化

划掉 App 再开,数据还在。

空输入

空白标题不会添加。

真机

自己手机上走一遍。

做完这一章,你已经走过「能跑的小产品」闭环。接下来才是网络同步、账号、推送等增强。

iOS App 从 0 到 1 · 主讲 SwiftUI / iOS