malaki1974
malaki1974

Reputation: 1623

Save / restore array of booleans to core date

Building my first SwiftUI app, and have some basic knowledge of Swift. So a bit much to chew but I am enjoying learning.

I have a Form with many toggles saving/restoring from core data in my swift app. Works well but the interface is cumbersome with all the toggles.

Instead I want to make an HStack of tappable labels that will be selected / unselected instead. Then when you submit it will map the selected Text objects to the existing State variables I have OR? save an array of selected strings to core data (for restoring later?).

In either case my code for this has been cobbled from a todo list tutorial plus some nice HStack examples I have put in my form. They select/deselect nicely but I do not know how to save their state like I did the toggle switches.

I will paste what I think is relevant code and remove the rest.

@State  var selectedItems: [String] = []

@State private var hadSugar = false
@State private var hadGluten = false
@State private var hadDairy = false

let dayvariablesText = [
      "Sugar",
      "Gluten",
      "Dairy"
    ]

// section 1 works fine
Section {
     VStack {
         Section(header: Text("Actions")) {
              Toggle("Sugar", isOn: $hadSugar)
              Toggle("Gluten", isOn: $hadGluten)
              Toggle("Dairy", isOn: $hadDairy)
         }
     }
}

// section 2 trying this
ScrollView(.horizontal) {
    LazyHGrid(rows: rows) {
         ForEach(0..<dayvariablesText.count, id: \.self) { item in
            GridColumn(item: dayvariablesText[item], items: $selectedItems)
         }
    }
}.frame(width: 400, height: 100, alignment: .topLeading)

// save
Button("Submit") {
     DataController().addMood(sugar: hadSugar, gluten: hadGluten, dairy: hadDairy, context: managedObjContext)
     dismiss()
}

This works fine with the toggles shown above - how to do this when selecting gridItems in the next section for example?

Upvotes: 1

Views: 55

Answers (1)

burnsi
burnsi

Reputation: 7744

I think you need to remodel your code. Having multiple sources of truth like in your example (with the vars and the array for the naming) is a bad practice and will hurt you in the long run.

Consider this solution. As there is a lot missing in your question it´s more general. So you need to implement it to fit your needs. But it should get you in the right direction.

//Create an enum to define your items
// naming needs some improvement :)
enum MakroType: String, CaseIterable{
    case sugar = "Sugar", gluten = "Gluten", dairy = "Dairy"
}

//This struct will hold types you defined earlier
// including the bool indicating if hasEaten
struct Makro: Identifiable{
    var id: MakroType {
        makro
    }
    var makro: MakroType
    var hasEaten: Bool
}

// The viewmodel will help you store and load the data
class Viewmodel: ObservableObject{
    //define and create the array to hold the Makro structs
    @Published var makros: [Makro] = []
    
    init(){
        // load the data either here or in the view
        // when it appears
        loadCoreData()
    }
    
    func loadCoreData(){
        //load items
        // ..... code here
        
        // if no items assign default ones
        if makros.isEmpty {
            makros = MakroType.allCases.map{
                Makro(makro: $0, hasEaten: false)
            }
        }
    }
    
    // needs to be implemented
    func saveCoreData(){
        print(makros)
    }
}

struct ContentView: View {
    // Create an instance of the Viewmodel here
    @StateObject private var viewmodel: Viewmodel = Viewmodel()
    
    var body: some View {
        VStack{
            ScrollView(.horizontal) {
                LazyHStack {
                    // Iterate over the items themselves and not over the indices
                    // with the $ in front you can pass a binding on to the ChildView
                    ForEach($viewmodel.makros) { $makro in
                        SubView(makro: $makro)
                     }
                }
            }.frame(width: 400, height: 100, alignment: .topLeading)
            Spacer()
            Button("Save"){
                viewmodel.saveCoreData()
            }.padding()
        }
        .padding()
    }
}

struct SubView: View{
    // Hold the binding to the Makro here
    @Binding var makro: Makro
    
    var body: some View{
        //Toggle to change the hasEaten Bool
        //this will reflect through the Binding into the Viewmodel
        Toggle(makro.makro.rawValue, isOn: $makro.hasEaten)
    }
}

Upvotes: 1

Related Questions