Persistent data when editing an existing array in swiftui

268 Views Asked by At

I am trying to figure out how to make two things work together. The first is persistent data using userdefaults, which I have figured out by using @Published and @Observable, then using JSONencoder/decoder to get that data to save even when the app is closed and reopened. The second I have also recently learned via this previous question < https://stackoverflow.com/questions/62106227/best-way-to-update-edit-an-array-element-in-swiftui/67752060#67752060>

My issue is combining the two. I can’t seem to figure out how to take the below code with predefined array of data and make the changes persist using UserDefaults. Any help would be greatly appreciated. Maybe I’m approaching this incorrectly.

Code for persistent data via UserDefaults


import SwiftUI

struct CharacterModel: Identifiable, Codable {
    
    var id = UUID()
    var name: String
    var level: Int
    
}

class CharacterViewModel: ObservableObject {
    
    @Published var characters = [CharacterModel]() {
        
        // Write data back to Model
        didSet {
            let encoder =  JSONEncoder()
            
            if let encoded = try?
                encoder.encode(characters) {
                UserDefaults.standard.set(encoded, forKey: "Characters")
            }
        }
    }
    
    init() {
        if let characters = UserDefaults.standard.data(forKey: "Characters") {
            let decoder = JSONDecoder()
            
            if let decoded = try?
                decoder.decode([CharacterModel].self, from: characters) {
                self.characters = decoded
                return
            }
        }
        
        self.characters = []
    }
}

struct DetailView: View {
    
    @Environment(\.presentationMode) var presentationMode
    @ObservedObject var characterVM: CharacterViewModel
    @State private var name = ""
    @State private var level = ""
    
    var body: some View {
        NavigationView {
            Form {
                TextField("Name", text: $name)
                
                TextField("Level", text: $level)
                    .keyboardType(.numberPad)
            }
            .navigationBarTitle("Add Level")
            .navigationBarItems(trailing: Button("Save") {
                if let actualLevel = Int(self.level) {
                    let character = CharacterModel(name: self.name, level: actualLevel)
                    self.characterVM.characters.append(character)
                    self.presentationMode.wrappedValue.dismiss()
                }
            })
        }
    }
}

struct DetailView_Previews: PreviewProvider {
    static var previews: some View {
        DetailView(characterVM: CharacterViewModel())
    }
}

struct Home: View {
    
    @State private var showingAddCharacter = false
    @State var selectedCharacter : CharacterModel!
    @ObservedObject var characterVM = CharacterViewModel()
    
    var body: some View {
        NavigationView {
            List {
                ForEach(characterVM.characters) { char in
                    HStack {
                        VStack(alignment: .leading) {
                            Text(char.name)
                                .font(.headline)
                        
                        }
                        Spacer()
                        Text("\(char.level)")
                    }
                }
                .onDelete(perform: removeItems)
            }

            .navigationBarTitle("Characters")
            .navigationBarItems(trailing:
                Button(action: {
                    self.showingAddCharacter = true
                }) {
                    Image(systemName: "plus")
                }
            )
            .sheet(isPresented: $showingAddCharacter) {
                DetailView(characterVM: self.characterVM)
            }
        }
    }
    
    func removeItems(at offsets: IndexSet) {
        characterVM.characters.remove(atOffsets: offsets)
    }
}

struct Home_Previews: PreviewProvider {
    static var previews: some View {
        Home()
    }
}

Editing a predefined array

class Training: ObservableObject, Identifiable {
    let id: String
    @Published var trainingName: String
    @Published var isRequired: Bool

    init(id: String, trainingName: String, isRequired: Bool) {
        self.id = id
        self.trainingName = trainingName
        self.isRequired = isRequired
    }
} 

class GetTrainings: ObservableObject {
    @Published var items = [Training]()

    init() {
        self.items = [
            Training(id: "ttt1", trainingName: "Safety", isRequired: true),
            Training(id: "ttt2", trainingName: "Administrative", isRequired: false),
            Training(id: "ttt3", trainingName: "Computer", isRequired: true),
            Training(id: "ttt4", trainingName: "People", isRequired: true),
            Training(id: "ttt5", trainingName: "Managerial", isRequired: true),
        ]
    }
}

struct TrainingList: View {

    @ObservedObject var trainings = GetTrainings()

    var body: some View {
        NavigationView {
            VStack {
                List {

                    ForEach(trainings.items) { training in

                        HStack {
                            NavigationLink(destination: TrainingView(training: training)) {
                                Text("\(training.trainingName)")
                            }
                        }
                    }

                }
            }.navigationBarTitle("Training List")
            .onAppear {
                self.trainings.objectWillChange.send() // refresh
            }
        }
    }
}

struct TrainingView: View {

    @ObservedObject var training: Training

    var body: some View {

        VStack {

            Text("\(training.trainingName)").font(.body)
            Text("\(training.isRequired == true ? "Required Training" : "Training Not Required")")

            HStack {
                NavigationLink(destination: EditTraining(training: training)) {
                    Text("Edit Training Details")
                }
            }
        }.navigationBarTitle("\(training.trainingName) Page", displayMode: .inline)

    }
}

struct EditTraining: View {

    @ObservedObject var training: Training

    @State private var newName: String
    @State private var isRequiredTraining: Bool

    init(training: Training) {
        self.training = training
        self._newName = State(initialValue: training.trainingName)
        self._isRequiredTraining = State(initialValue: training.isRequired)
    }

    private func submitData() {

        let newName = self.newName
        let newBoolVal = self.isRequiredTraining

        print("Firebase Sync Id is :\(training.id) Text: \(newName) and Bool: \(newBoolVal)")

        self.training.trainingName = newName
        self.training.isRequired = newBoolVal
    }

    var body: some View {
        VStack {
            Form {
                Section (header: Text("Edit"))  {

                    Text("\(training.trainingName)")
                    /* TextField should Populate With passed In Training Name Here*/
                    TextField("New Name", text: self.$newName)
                    Toggle(isOn: self.$isRequiredTraining) {
                        Text("Is Required")
                    }
                }

                Section {

                    Button(action: {
                        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                            to:nil, from:nil, for:nil)
                        self.submitData()
                    }) {
                        Text("Submit")
                    }

                }
            }
        }.navigationBarTitle("Edit Training Page", displayMode: .inline)
    }
} 
0

There are 0 best solutions below