置顶
菜鸟入门,各位大佬轻喷,如有谬误之处欢迎讨论建议,也欢迎各位道友与我同行
“不积跬步,无以至千里;不积小流,无以成江海”
继续
上文中已经实现了 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()
}
}
总结
-
ObservableObject
与@Published
是在主动定义一个可观察的对象,虽然可以添加许多操作,但是感觉上反而不如reactive
方法更简洁 - 目前看来,
Struct
一般用来定义结构或者视图,Class
才是我们普遍意义上的类。 - 数据抽象虽然简化了View,但是感觉比较繁琐,或许我应该考虑直接都放到
Struct View
里面去,参考Vue
的结构进行编排,各有优劣,后头试试 - 横滑删除的功能非常好实现,直接在
Foreach
后加上.onDelete
即可 -
SwiftUI
中有很多这种加方法调用即可实现视图以及功能的方法,也许可以参考到Vue
或者其他的前端封装里面 - 想实现一个
computed
变量,直接var
一个,然后在底下写方法即可
网友评论