How to save "Checked / Unchecked Tasks" to persist data with UserDefaults?
In my ListViewModel, I'm attempting to save the checkmarks Bool isCompleted state to UserDefaults. When you relaunch the App, the checkmarks reset to false.
Not sure if I need to add an EnvironmentObject or ObservableObject to the 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)
}
}
}