Trouble implementing UserDefaults

Viewed 103

I have previous coding experience but I'm extremely new to both Swift and iOS. I'm developing an app for personal use to coalesce the functions of multiple different apps I use to help manage ADHD into one place. The main view of the app provides a way to track daily expenditures. I'm trying to use UserDefaults to store the information entered so it will still be there when I reopen the app. The app runs fine, and as near as I can figure out I've written the data handling right, but it simply doesn't work. I've been beating my head against the wall at this for the past few days including on this website, so any help would be greatly appreciated. Here's the code for the main view of the app:

    import SwiftUIFontIcon
import SwiftUI
//import UIKit

struct ContentView: View {
    
    @State public var purchases = [Purchases]()
    
    @State public var prices = [Price]()
    
    @State public var isActive = false
    
    @State public var goTo: String = ""
    
    @State public var purchase: String = ""
    
    @State public var price: String = ""
    
    //    @State public var isActive: Bool = false
    
    init(){
        if let data = UserDefaults.standard.data(forKey: "Purchases"){
            if let decoded = try? JSONDecoder().decode([Purchases].self, from: data){
                self.purchases = decoded
                
            }
            return
        }
        self.purchases = []
        if let data2 = UserDefaults.standard.data(forKey: "Bread"){
            if let decoded2 = try? JSONDecoder().decode([Price].self, from: data2){
                self.prices = decoded2
                
            }
            return
        }
        self.prices = []
        
    }
    
    func addItem(){
        
        saveStuff()
        self.purchases.append(Purchases(name: purchase))
        saveStuff()
        purchase = ""
        
    }
    func addPrice(){
        saveBread()
        self.prices.append(Price(name: price))
        saveBread()
        price = ""
        
    }
    func deleteItem(at offsets: IndexSet){
        purchases.remove(atOffsets: offsets)
    }
    func deletePrice(at offsets: IndexSet){
        prices.remove(atOffsets: offsets)
    }
    func saveStuff(){
        if let encodedData = try? JSONEncoder().encode(purchases){
            UserDefaults.standard.set(encodedData, forKey: "Purchases")
        }
        //    return
    }
    
    func saveBread(){
        if let encodedData = try? JSONEncoder().encode(prices){
            UserDefaults.standard.set(encodedData, forKey: "Bread")
        }
        //    return
    }
    func clearList(){
        self.prices.removeAll()
        self.purchases.removeAll()
    }
    
    var body: some View {
        
        NavigationView{
            ZStack {
                Color.gray
                    .edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
                VStack {
                    //                    Spacer()
                    //                    HStack {
                    //                        //                                    Spacer()
                    //                        NavigationLink(
                    //                            destination: ToDoList(rootIsActive: self.$isActive),
                    //                            isActive: self.$isActive
                    //
                    //                        ){
                    //                            FontIcon.text(.ionicon(code: .ios_list_box), fontsize: 48, color: .black)
                    //                        }
                    //                        Spacer()
                    //                        NavigationLink(destination: ReminderView()){
                    //                            FontIcon.text(.ionicon(code: .ios_warning), fontsize: 48, color: .black)
                    //                        }
                    //                        //                                            Spacer()
                    //                    }
                    Spacer()
                    VStack{
                        Spacer()
                        HStack {
                            Spacer()
                            HStack {
                                Spacer()
                                TextField("Add an Item", text: $purchase)
                                    .padding(12)
                                    .border(Color.black)
                                Spacer()
                                Spacer()
                                Spacer()
                                TextField("Add a Price", text: $price)
                                    .padding(12)
                                    .border(Color.black)
                                //                                Spacer()
                                
                                FontIcon.button(.ionicon(code: .ios_add_circle), action: {
                                    addItem()
                                    addPrice()
                                    saveStuff()
                                    saveBread()
                                }, padding: 12, fontsize: 45, color: .black)
                            }
                            .opacity(1)
                            .padding(12)
                            //                            .border(Color.black)
                            //                            Spacer()
                        }
                        Spacer()
                        VStack {
                            HStack {
                                Spacer()
                                List{
                                    ForEach(purchases){ purchase in
                                        Text(purchase.name)
                                            .cornerRadius(16)
                                            .padding(10)
                                    }
                                    
                                    .onDelete(perform: deleteItem)
                                    //                                    .listStyle(GroupedListStyle())
                                    
                                }
                                Spacer()
                                List{
                                    ForEach(prices){ price in
                                        Text(price.name)
                                            .cornerRadius(16)
                                            .padding(10)
                                    }
                                    .onDelete(perform: deletePrice)
                                    //                                    .onAppear{UITableView.appearance().separatorColor = .clear}
                                }
                                Spacer()
                                
                            }
                            Spacer()
                            HStack {
                                //                                Spacer()
                                //                                FontIcon.button(.ionicon(code: .ios_save), action: {
                                //                                   saveStuff()
                                //                                    saveBread()
                                //                                }, padding: 12, fontsize: 78, color: .green)
                                Spacer()
                                
                                FontIcon.button(.ionicon(code: .ios_trash),action:{
                                    clearList()
                                }, padding: 12, fontsize: 78, color: .red)
                                Spacer()
                                NavigationLink(destination: ViewLists()){
                                    FontIcon.text(.ionicon(code: .ios_filing), fontsize: 58, color: .black)
                                }
                                Spacer()
                                NavigationLink(destination: ViewTotals()){
                                    FontIcon.text(.ionicon(code: .ios_add), fontsize: 58, color: .black)
                                }
                                Spacer()
                                NavigationLink(destination: IncomeView()){
                                    FontIcon.text(.ionicon(code: .ios_musical_note), fontsize: 58, color: .black)
                                    
                                }
                                Spacer()
                                
                            }
                            //                            Spacer()
                        }
                        Spacer()
                    }
                    Spacer()
                }
                .navigationBarTitle("ADD ToolKit", displayMode: .large)
                Spacer()
                //                    .navigationBarTitle("Ledger", displayMode: .large)
                //                Spacer()
            }
            .toolbar{
                ToolbarItem(placement: .primaryAction){
                    //                                                Spacer()
                    //                                            Spacer()
                    HStack {
                        Spacer()
                        NavigationLink(destination: ReminderView()){
                            FontIcon.text(.ionicon(code: .ios_warning), fontsize: 48, color: .black)
                            
                            //                                            Spacer()
                        }
                    }
                    
                }
                ToolbarItem(placement: .navigationBarLeading){
                    HStack {
                        
                        Spacer()
                        NavigationLink(
                            destination: ToDoList(rootIsActive: self.$isActive),
                            isActive: self.$isActive
                            
                        ){
                            FontIcon.text(.ionicon(code: .ios_list_box), fontsize: 48, color: .black)
                        }
                        
                        
                    }
                    
                }
                
            }
            
            
            
            //
            
            
            
            
            
            
            
        }
        
        .background(
            NavigationLink(destination: Text(self.goTo), isActive: $isActive){
                EmptyView()
            })
    }
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

and the code for the model where I define all the array structs:

import Foundation
import SwiftUI

struct Purchases: Identifiable, Codable{
    let id: String
    let name: String
    
    init(id: String = UUID().uuidString, name: String){
        self.id = id
        self.name = name
    }
}

struct Price: Identifiable, Codable{
    let id: String
    let name: String
    
    init(id: String = UUID().uuidString, name: String){
        self.id = id
        self.name = name
    }
}

struct ToDo: Identifiable, Codable{
    let id: String
    let name: String
    
    init(id: String = UUID().uuidString, name: String){
        self.id = id
        self.name = name
    }
}
struct Reminder: Identifiable, Codable{
    let id: String
    let name: String
    
    init(id: String = UUID().uuidString, name: String){
        self.id = id
        self.name = name
    }
}

struct Income: Identifiable, Codable{
    let id: String
    let name: String
    
    init(id: String = UUID().uuidString, name: String){
        self.id = id
        self.name = name
    }
}

Thanks in advance

1 Answers

Add the below code to a .swift file in your project

//Allows all Codable Arrays to be saved using 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
    }
}

Then use @AppStorage vs @State for the arrays you want saved

@AppStorage("Purchases") var purchases: [Purchases] = []
@AppStorage("Bread") var breadPrices: [Price] = []

You can write to them as you would to a regular array

You don't need saveStuff or saveBread

Below is a simplified version of your code. I couldn't reproduce yours to test.

import SwiftUI

struct CodableUserDefaultView: View {
    
    @AppStorage("Purchases") var purchases: [Purchases] = []
    //@State public var purchases = [Purchases]()
    @AppStorage("Bread") var breadPrices: [Price] = []
    //@State public var breadPrices = [Price]()
    
    @State public var isActive = false
    
    @State public var goTo: String = ""
    
    
    @State public var price: String = ""
    
    func addItem(purchaes: Purchases){
        
        self.purchases.append(purchaes)
        
    }
    func addPrice(price: Price){
        self.breadPrices.append(price)
        
    }
    func deleteItem(at offsets: IndexSet){
        purchases.remove(atOffsets: offsets)
    }
    func deletePrice(at offsets: IndexSet){
        breadPrices.remove(atOffsets: offsets)
    }
    
    func clearList(){
        self.breadPrices.removeAll()
        self.purchases.removeAll()
    }
    
    var body: some View {
        List{
            Section(content: {
                ForEach(breadPrices){ price in
                    HStack{
                        Text(price.name)
                        Spacer()
                        Button("purchase", action: {
                            addItem(purchaes: Purchases(name: price.name))
                        })
                    }
                }.onDelete(perform: deletePrice)
                VStack{
                    Text("Bread")
                    TextField("Bread Price", text: $price, onCommit: {
                        addPrice(price: Price(name: price))
                    })
                }
            }, header: {
                Text("Bread")
            })
            Section(content: {
                ForEach(purchases){ purchase in
                    HStack{
                        Text(purchase.name)
                    }
                }.onDelete(perform: deleteItem)
            }, header: {
                Text("Purchases")
            })
        }
    }
}



struct CodableUserDefaultView_Previews: PreviewProvider {
    static var previews: some View {
        CodableUserDefaultView()
    }
}

But as mentioned in the comments this really isn't a good use for UserDefaults. It is meant for smaller stuff.

The defaults system allows an app to customize its behavior to match a user’s preferences. For example, you can allow users to specify their preferred units of measurement or media playback speed. Apps store these preferences by assigning values to a set of parameters in a user’s defaults database.

https://developer.apple.com/documentation/foundation/userdefaults

You might want to look into Core Data if you are staying iOS only or another Database system like Firebase, AWS, Azure, etc.

Related