置頂
菜鳥入門,各位大佬輕噴,如有謬誤之處歡迎讨論建議,也歡迎各位道友與我同行
“不積跬步,無以至千裡;不積小流,無以成江海”
繼續
上文中我們解實作 todo 的詳情表單,即在 List 中點選每一項彈出一個 todo 的表單,裡面可以修改 todo 的名稱等。
但是發現有一個問題,即每次退出程式時,我們已有的 todo 項全部被重置,再次打開又成了預設的那一條。
是以,本文我們将來實作普通資料的本地持久化儲存,包括存儲與取消存儲。效果如下:
思考
之前我們用過 @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)
}
}
}
}
總結
- @AppStorage 的存儲時機不确定,可能存在丢失的可能,以後再研究,目前僅實作功能。
- @AppStorage 是儲存和讀取 UserDefaults 變量的一種快捷方式,即本身就是 UserDefaults。但隻支援幾種基礎資料類型,其它的需要自己擴充
- SwiftUI 裡的 try 有三種方式: try? 會在出錯時,傳回 nil,不會導緻程式崩潰,如果沒有錯誤,會傳回一個可選值,try! 會在出錯時崩潰,打破錯誤傳播鍊,當然還有 do...catch
- JSONDecoder 和 JSONEncoder 的基本使用