天天看點

幹貨-swift實作todo狀态切換

作者:思躍喵

置頂

菜鳥入門,各位大佬輕噴,如有謬誤之處歡迎讨論建議,也歡迎各位道友與我同行
“不積跬步,無以至千裡;不積小流,無以成江海”
幹貨-swift實作todo狀态切換

繼續

上文中已經實作了 TODO 頁面的基本新增邏輯以及删除功能

本文将實作資料每一個 TODO 項的完成狀态切換、建立時間

以及滑動删除功能。

同時完成一個資料的抽象,即将資料處理的部分抽象到一個對象内,頁面中隻管調用即可。

最終效果如下:

幹貨-swift實作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 一個,然後在底下寫方法即可