如何保存“选中/未检查的任务”以使用UserDefaults持久化数据?
在我的ListViewModel,中,我试图将检查点Bool isCompleted状态保存到UserDefaults。当您重新启动应用程序时,复选标记将重置为false。
不确定是否需要将EnvironmentObject或ObservableObject添加到ListView中
ListViewModel.swift
//
// ListViewModel.swift
//
import Foundation
class ListViewModel: ObservableObject {
@Published var items: [ItemModel] = [] {
didSet {
saveItems()
}
}
let itemsKey: String = "task_items_list"
init() {
myTaskItems()
}
func myTaskItems() {
let taskItems = [
ItemModel(title: "This is the first task!", isCompleted: false),
ItemModel(title: "This is the second task!", isCompleted: false),
ItemModel(title: "This is the third task!", isCompleted: false),
ItemModel(title: "This is the fourth task!", isCompleted: false),
ItemModel(title: "This is the fifth task!", isCompleted: false)
]
items.append(contentsOf: taskItems)
guard
let data = UserDefaults.standard.data(forKey: itemsKey),
let savedTaskItems = try? JSONDecoder().decode([ItemModel].self, from: data)
else { return }
self.items = savedTaskItems
}
// Update TaskdItems Toggle
func updateItem(item:ItemModel) {
if let index = items.firstIndex(where: { $0.id == item.id }) {
items[index] = item.updateCompletion()
}
}
// Save to UserDefaults
func saveItems() {
if let encodedData = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(encodedData, forKey: itemsKey)
}
}
}
ItemModel.swift
// ItemModel.swift
//
import Foundation
struct ItemModel:Identifiable, Codable {
let id: String
let title: String
let isCompleted: Bool
init(id: String = UUID().uuidString, title: String, isCompleted: Bool) {
self.id = UUID().uuidString
self.title = title
self.isCompleted = isCompleted
}
func updateCompletion() -> ItemModel {
return ItemModel(id: id, title: title, isCompleted: !isCompleted)
}
}
ListView.swift
//
// ListView.swift
//
import SwiftUI
struct ListView: View {
@EnvironmentObject var listViewModel: ListViewModel
var body: some View {
List {
ForEach(listViewModel.items) { item in
ListRowView(item: item)
.onTapGesture {
listViewModel.updateItem(item: item)
}
}
}
.listStyle(PlainListStyle())
.navigationTitle("My Task List")
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ListView()
}
.environmentObject(ListViewModel())
}
}
}
ListRowView.swift
//
// ListRowView.swift
//
import SwiftUI
struct ListRowView: View {
let item: ItemModel
var body: some View {
HStack {
Image(systemName: item.isCompleted ? "checkmark.circle" : "circle")
.foregroundColor(item.isCompleted ? .green : .gray)
Text(item.title)
Spacer()
}
.font(.title2)
.padding(.vertical, 8)
}
}
struct ListRowView_Previews: PreviewProvider {
static var item1 = ItemModel(title: "First item!", isCompleted: false)
static var item2 = ItemModel(title: "Second item!", isCompleted: true)
static var previews: some View {
Group {
ListRowView(item: item1)
ListRowView(item: item2)
}
.previewLayout(.sizeThatFits)
}
}
TaskListApp.swift
//
// TaskListApp.swift
//
import SwiftUI
@main
struct TaskListApp: App {
@StateObject var listViewModel: ListViewModel = ListViewModel()
var body: some Scene {
WindowGroup {
NavigationView {
ListView()
}
.environmentObject(listViewModel)
}
}
}
发布于 2022-09-11 00:29:26
这里的问题是您的myTask
函数。每次初始化ListViewmodel
时,都是使用初始数组覆盖UserDefaults
。将该代码移动到else
语句块中。
func myTaskItems() {
guard
let data = UserDefaults.standard.data(forKey: itemsKey),
let savedTaskItems = try? JSONDecoder().decode([ItemModel].self, from: data)
else {
let taskItems = [
ItemModel(title: "This is the first task!", isCompleted: false),
ItemModel(title: "This is the second task!", isCompleted: false),
ItemModel(title: "This is the third task!", isCompleted: false),
ItemModel(title: "This is the fourth task!", isCompleted: false),
ItemModel(title: "This is the fifth task!", isCompleted: false)
]
items.append(contentsOf: taskItems)
return
}
这确实解决了这个问题。
但是,我想请您考虑一种不同的方法,使您的代码更容易。您可以结合使用@Appstorage
和@Binding
。在视图模型中使用@Appstorage
,并通过Binding
将ItemsModel
传递给ListRowView
。
我已经对代码进行了评论,因为我认为它可能会有所帮助。如果您对此实现有疑问,请随意提问。
//needed to use array in @Appstorage
extension Array: RawRepresentable where Element: Codable{
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode([Element].self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return ""
}
return result
}
}
class ListViewModel: ObservableObject {
// this loads and saves the items automatic to UserDefaults
@AppStorage("task_items_list") var items: [ItemModel] = []
init() {
//fill the collection only if it is empty
if items.isEmpty{
myTaskItems()
}
}
func myTaskItems() {
items = [
ItemModel(title: "This is the first task!", isCompleted: false),
ItemModel(title: "This is the second task!", isCompleted: false),
ItemModel(title: "This is the third task!", isCompleted: false),
ItemModel(title: "This is the fourth task!", isCompleted: false),
ItemModel(title: "This is the fifth task!", isCompleted: false)
]
}
//The rest of the functions are not needed
}
struct ItemModel: Identifiable, Codable {
var id: String = UUID().uuidString
var title: String
var isCompleted: Bool
}
struct ListView: View {
@EnvironmentObject var listViewModel: ListViewModel
var body: some View {
List {
//Use the $ syntax to pass a binding on to the subviews
ForEach($listViewModel.items) { $item in
ListRowView(item: $item)
}
}
.listStyle(PlainListStyle())
.navigationTitle("My Task List")
}
}
struct ListRowView: View {
//this binding will assure changes will bubble up into the viewmodel and will be saved in UserDefaults
@Binding var item: ItemModel
var body: some View {
HStack {
Image(systemName: item.isCompleted ? "checkmark.circle" : "circle")
.foregroundColor(item.isCompleted ? .green : .gray)
Text(item.title)
Spacer()
}
.font(.title2)
.padding(.vertical, 8)
// this is enough to toggle the checkmark
.onTapGesture {
item.isCompleted.toggle()
}
}
}
https://stackoverflow.com/questions/73675830
复制相似问题