干货swift实现todo状态切换
置顶
菜鸟入门,各位大佬轻喷,如有谬误之处欢迎讨论建议,也欢迎各位道友与我同行
"不积跬步,无以至千里;不积小流,无以成江海"
继续
上文中已经实现了 TODO 页面的基本新增逻辑以及删除功能
本文将实现数据每一个 TODO 项的完成状态切换、创建时间
以及滑动删除功能。
同时完成一个数据的抽象,即将数据处理的部分抽象到一个对象内,页面中只管调用即可。
最终效果如下:
思考
还是老规矩,既然要抽象一个数据模型出来,那就是一个独立的文件。
一个关于 TODO 的数据模型。
至少有两个 struct ,一个 todoItem 的定义,另一个是 todoList 的定义
这个数据模型中是所有的关于这个 todo lists 的操作
如果所有的操作都集中在这个模型中,那我我们的 todo 页面中的所有操作即可调用这个数据模型。 实现
我们新增一个 TodoModel.swift ,内容如下: import SwiftUI; // 这里是定义 todo 项的数据结构,结构体用于定义结构,类用于定义完整数据对象 struct TodoItem:Identifiable,Equatable{ // 给生成一个唯一的id作为标识,相当于实现了 Identifiable let id = UUID(); // todo项名称 var name:String ; // 是否已经完成,默认为false var isFinished:Bool = false; // 创建时间 var createTime:Int = 0; // 完成时间 var finishTime:Int = 0; // 用来展示的时间,这里相当于是个 computed var createdAt:String { // 将时间戳转为时间字符串 if(createTime == 0) { return ""; } let date:Date = Date.init(timeIntervalSince1970: Double(createTime)) let formatter = DateFormatter.init() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" return formatter.string(from: date as Date) } } // ObservableObject 代表这是一个可以被观察的对象 class TodoLists : ObservableObject { // @Published 代表这个变量的任何变化都会被发布到外面使用这个变量的地方,更新视图 // private(set) 代表这个变量的设置、修改等对外隐藏,但是对外可见 // [TodoItem] 代表这是一个数组,里面的每一条数据都是 TodoItem 类型的 @Published private(set) var todoList:[TodoItem]; init(todoList: [TodoItem]) { self.todoList = todoList // 如果是个空数组,那么先放一个进去 if(todoList.count == 0 ){ add(name: "请添加TODO") } } // 添加一条 todo项,只要名称即可 func add(name:String){ if(name == ""){ return ; } var item = TodoItem(name: name); item.createTime = Int(Date().timeIntervalSince1970); self.todoList.insert(item,at: 0); } // 切换todo项的是否完成状态,如果完成状态为true那更新finishTime func toggle(item:TodoItem){ // 找到这一条的索引 index,$0代表这个方法的第一个参数 let index = todoList.firstIndex(where: {$0.id == item.id}) if index != nil { // index! 代表我知道这个index一定存在,不用再进行判断了 todoList[index!].isFinished.toggle() // 如果是完成,那么更新已完成时间,否则改为0 if(todoList[index!].isFinished == true){ todoList[index!].finishTime = Int(Date().timeIntervalSince1970); }else{ todoList[index!].finishTime = 0; } } } // 删除todo func delete(offsets: IndexSet){ offsets.forEach { index in todoList.remove(at: index) } } }
既然我们数据模型已经定义好了,那么自然要修改 TodoView.swift 页面中的使用
修改如下:
TodoView.swift import SwiftUI struct TodoView: View { // 是否已经登陆 @AppStorage("isLogin") private var isLogin:Bool = false; // 已经登陆的用户名 @AppStorage("userName") private var userName:String = ""; // 输入框输入的新的TODO @State private var newItem:String = ""; // 使用我们新的数据模型 @StateObject private var todos = TodoLists(todoList: []); var body: some View { VStack{ HStack{ TextField("请输入新的TODO",text:$newItem).onSubmit { todos.add(name: newItem) newItem = "" } Button("确认"){ todos.add(name: newItem) newItem = "" } }.padding() List{ // Foreach 开始循环 TodoLists 的indices,需要它的索引值,用于删除等 // id 需要为一个 Identifier,可以预见,之后我们自己构造数据类型的时候也需要一个 Identifier ForEach(todos.todoList){ item in HStack{ VStack{ HStack{ // 字符串拼接,之前已有使用 Text("(item.name)") Spacer() } HStack{ Text("(item.createdAt)").font(.subheadline) Spacer() } }.foregroundColor(item.isFinished ? .gray : .primary) // 这里用个Group套起来,里面用三元实现点击切换图标,展示是否已经完成 Group{ item.isFinished ? Image(systemName: "circle.fill") : Image(systemName: "circle") } }.contentShape(Rectangle()) .onTapGesture { todos.toggle(item: item) } // 这个调用将实现横滑删除功能 }.onDelete{ IndexSet in todos.delete(offsets: IndexSet) } }.animation(.default,value:todos.todoList) } } } struct TodoView_Previews: PreviewProvider { static var previews: some View { TodoView() } }总结1. ObservableObject 与 @Published 是在主动定义一个可观察的对象,虽然可以添加许多操作,但是感觉上反而不如 reactive 方法更简洁2. 目前看来, Struct 一般用来定义结构或者视图,Class 才是我们普遍意义上的类。3. 数据抽象虽然简化了View,但是感觉比较繁琐,或许我应该考虑直接都放到 Struct View 里面去,参考 Vue 的结构进行编排,各有优劣,后头试试4. 横滑删除的功能非常好实现,直接在 Foreach 后加上 .onDelete 即可5. SwiftUI 中有很多这种加方法调用即可实现视图以及功能的方法,也许可以参考到 Vue 或者其他的前端封装里面6. 想实现一个 computed 变量,直接 var 一个,然后在底下写方法即可