置頂
菜鳥入門,各位大佬輕噴,如有謬誤之處歡迎讨論建議,也歡迎各位道友與我同行
“不積跬步,無以至千裡;不積小流,無以成江海”
繼續
上文中已經實作了 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 一個,然後在底下寫方法即可