Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
  • 案例:实现一个简单的 Todo 数据模型

案例:实现一个简单的 Todo 数据模型

目标

通过实现一个基础的 Todo 数据模型,理解 SwiftUI 中 Model 层的设计原则和实现方式。

核心概念

1. 数据模型定义

struct TodoItem: Identifiable, Codable {
    let id: UUID
    var title: String
    var isCompleted: Bool
    var dueDate: Date?
    
    init(title: String, isCompleted: Bool = false, dueDate: Date? = nil) {
        self.id = UUID()
        self.title = title
        self.isCompleted = isCompleted
        self.dueDate = dueDate
    }
}

关键特性:

  • 遵循 Identifiable 协议(SwiftUI 列表必需)
  • 遵循 Codable 协议(支持持久化)
  • 使用值类型(struct)确保数据不可变性

2. 模型操作方法

extension TodoItem {
    mutating func toggleCompletion() {
        isCompleted.toggle()
    }
    
    func isOverdue(now: Date = Date()) -> Bool {
        guard let dueDate = dueDate else { return false }
        return now > dueDate
    }
}

3. 模型容器

class TodoStore: ObservableObject {
    @Published var items: [TodoItem] = []
    
    func addItem(_ title: String) {
        let newItem = TodoItem(title: title)
        items.append(newItem)
    }
    
    func removeItems(at indices: IndexSet) {
        items.remove(atOffsets: indices)
    }
}

完整实现示例

数据持久化扩展

extension TodoStore {
    private static func fileURL() throws -> URL {
        try FileManager.default.url(for: .documentDirectory,
                                   in: .userDomainMask,
                                   appropriateFor: nil,
                                   create: false)
        .appendingPathComponent("todos.data")
    }
    
    func load() throws {
        let url = try Self.fileURL()
        let data = try Data(contentsOf: url)
        items = try JSONDecoder().decode([TodoItem].self, from: data)
    }
    
    func save() throws {
        let data = try JSONEncoder().encode(items)
        let url = try Self.fileURL()
        try data.write(to: url)
    }
}

使用案例

// 创建实例
let store = TodoStore()

// 添加数据
store.addItem("学习SwiftUI")
store.addItem("完成MVVM项目")

// 修改数据
store.items[0].toggleCompletion()

// 持久化操作
do {
    try store.save()
} catch {
    print("保存失败: \(error)")
}

设计要点

  1. 不可变标识符:使用 UUID 确保唯一性
  2. 可测试性:所有业务逻辑(如 isOverdue)不依赖外部状态
  3. 关注点分离:
    • 模型只包含数据和基础操作
    • 存储类处理集合操作和持久化
  4. SwiftUI 集成准备:
    • ObservableObject 协议支持数据绑定
    • @Published 属性包装器自动触发视图更新

常见问题解决方案

问题1:如何处理日期显示?

extension TodoItem {
    var dueDateString: String {
        guard let date = dueDate else { return "无期限" }
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        return formatter.string(from: date)
    }
}

问题2:如何实现排序功能?

extension TodoStore {
    func sortByDueDate(ascending: Bool = true) {
        items.sort {
            switch ($0.dueDate, $1.dueDate) {
            case (nil, nil): return false
            case (nil, _): return false
            case (_, nil): return true
            case (let lhs?, let rhs?):
                return ascending ? lhs < rhs : lhs > rhs
            }
        }
    }
}

提示:在实际项目中,建议将日期格式化等显示逻辑放在 ViewModel 层,保持 Model 只关注核心数据。

Last Updated:: 4/25/25, 8:06 PM