置顶
菜鸟入门,各位大佬轻喷,如有谬误之处欢迎讨论建议,也欢迎各位道友与我同行
“不积跬步,无以至千里;不积小流,无以成江海”
继续
上文中我们解实现 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
的基本使用