天天看點

SwiftUI入門 - TodoLists資料的本地持久化

作者:思躍喵
SwiftUI入門 - TodoLists資料的本地持久化

置頂

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

繼續

上文中我們解實作 todo 的詳情表單,即在 List 中點選每一項彈出一個 todo 的表單,裡面可以修改 todo 的名稱等。

但是發現有一個問題,即每次退出程式時,我們已有的 todo 項全部被重置,再次打開又成了預設的那一條。

是以,本文我們将來實作普通資料的本地持久化儲存,包括存儲與取消存儲。效果如下:

SwiftUI入門 - TodoLists資料的本地持久化

思考

之前我們用過 @AppStorage 來進行持久化存儲,但是 @AppStorage 隻支援幾個基礎的資料類型,我們構造的 TodoLists 顯然不是個基礎類型。

但 @AppStorage 确實挺好用的,我們拿它來進行初始化或者儲存資料是可以的,既然它隻支援基礎類型,那麼我們将 TodoLists 用JSONEncoder 轉化成 String,進行存取應是可行的。

那麼基本的實作方案即修改 TodoModel 添加一個 saveData 方法和 clearData 方法,并修改 TodoModel 的 init 方法 ,優先使用 @AppStorage 中的資料

實作

修改 TodoModel.swfit

import SwiftUI;
// 一定要添加 Encodable 和 Decodable
struct TodoItem:Identifiable,Equatable,Encodable,Decodable{
    // 把 let 改成var,現在需要id可以被修改了
    var id = UUID();
    var name:String ;
    var isFinished:Bool = false;
    var createTime:Int = 0;
    var finishTime:Int = 0;
    var createdAt:String {
        // ...
    }
}

class TodoLists : ObservableObject { 
    // 添加 @AppStorage 參數
    @AppStorage("todoLists") public var store:String = "";
    @Published private(set) var todoList:[TodoItem];
    init(todoList: [TodoItem]) {
        self.todoList = todoList
        // 如果是個空數組,那麼先放一個進去
        if(todoList.count == 0 ){
            // 使用 @AppStorage 裡面的資料來進行初始化
            if(store == ""){
                // 本身就沒有值的時候新增一條預設的
                add(name: "請添加TODO")
            }else{
                // 這裡在将 @AppStorage 中的值進行解碼,然後放到 todoList 變量上去
                guard (try? self.todoList = JSONDecoder().decode([TodoItem].self, from: store.data(using: .utf8)!)) != nil else{
                    return;
                }
            }
        }
    }
    // 添加一條 todo項,隻要名稱即可
    func add(name:String){
        // ...
        saveData()
    }
    // 切換todo項的是否完成狀态,如果完成狀态為true那更新finishTime
    func toggle(item:TodoItem){
        // ...
        saveData()
    }
    // 删除todo
    func delete(offsets: IndexSet,isFinished:Bool = false){
        // ...
        saveData()
    }
    // 更新資料
    func update(item:TodoItem){
        // ...
        saveData()
    }
    // 将資料儲存到 @AppStorage
    func saveData(){
        // 将 todoList JSON化後存入 store變量中,利用 @AppStorage 進行存儲
        guard let data = try? JSONEncoder().encode(self.todoList) else{
            return;
        }
        store = String(data:data, encoding: .utf8)!;
    }
    // 清除所有的todo項
    func clear(){
        todoList = [];
        saveData();
    }
}
           

既然已經有了清除所有資料的方法,那麼我們就可以在 Setting 頁面中添加一個按鈕,用于清除所有的 todo 項,同時顯示已有多少條

先将 IndexView 中的 EnvironmentObject 挂到最外層,保證 Setting 能夠取得到這個全局執行個體。

//  IndexView.swift
import SwiftUI
struct IndexView: View{
    // ...
    let todos = TodoLists(todoList: [])
    
    var body: some View{
        // ...
        VStack{
            // 一個簡單的tabview,底部導航欄
            TabView {
                TodoView()
                    .tabItem {
                        Image(systemName: "list.dash")
                        Text("TODO")
                    }.tag(0)
                SettingView()
                    .tabItem {
                        Image(systemName: "gear.circle")
                        Text("設定")
                    }.tag(1)
            }
            .font(.headline)
        }.environmentObject(todos)
    }
}           

修改 Setting.swift 如下:

//  SettingView.swift
import SwiftUI
struct SettingView: View {
    @AppStorage("isLogin") private var isLogin:Bool = false;
    @AppStorage("userName") private var userName:String = "";
    // 從 EnvironmentObject 中 拿出 todos
    @EnvironmentObject var todos:TodoLists;
    var body:some View{
        NavigationView{
            if(isLogin){
                List{
                    // ...
                    Section{
                        HStack{
                            Spacer()
                            Text("清除所有的Todo,共\(todos.todoList.count)條").foregroundColor(.red)
                            Spacer()
                        }.onTapGesture {
                            todos.clear()
                        }
                    }
                    Section{
                        HStack{
                            Spacer()
                            Text("退出登陸").foregroundColor(.red)
                            Spacer()
                        }.onTapGesture {
                            isLogin = false;
                            // 退出登陸時也應當清楚 todos
                            todos.clear()
                        }
                    }
                }.navigationTitle("設定")
            }else{
                Text("請登入").foregroundColor(.red)
            }
        }
    }
}           

總結

  1. @AppStorage 的存儲時機不确定,可能存在丢失的可能,以後再研究,目前僅實作功能。
  2. @AppStorage 是儲存和讀取 UserDefaults 變量的一種快捷方式,即本身就是 UserDefaults。但隻支援幾種基礎資料類型,其它的需要自己擴充
  3. SwiftUI 裡的 try 有三種方式: try? 會在出錯時,傳回 nil,不會導緻程式崩潰,如果沒有錯誤,會傳回一個可選值,try! 會在出錯時崩潰,打破錯誤傳播鍊,當然還有 do...catch
  4. JSONDecoder 和 JSONEncoder 的基本使用