干货-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. 1. ObservableObject@Published 是在主动定义一个可观察的对象,虽然可以添加许多操作,但是感觉上反而不如 reactive 方法更简洁
  2. 2. 目前看来,Struct 一般用来定义结构或者视图,Class 才是我们普遍意义上的类。
  3. 3. 数据抽象虽然简化了View,但是感觉比较繁琐,或许我应该考虑直接都放到 Struct View里面去,参考 Vue 的结构进行编排,各有优劣,后头试试
  4. 4. 横滑删除的功能非常好实现,直接在 Foreach后加上 .onDelete 即可
  5. 5. SwiftUI 中有很多这种加方法调用即可实现视图以及功能的方法,也许可以参考到 Vue 或者其他的前端封装里面
  6. 6. 想实现一个 computed变量,直接 var 一个,然后在底下写方法即可
展开阅读全文

页面更新:2024-03-13

标签:干货   视图   变量   抽象   定义   状态   代表   数据模型   功能   时间   方法   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top