Home > front end >  Looping over the properties of Views (struct) in an Array is returning wrong values (init values)
Looping over the properties of Views (struct) in an Array is returning wrong values (init values)

Time:04-02

I am using an array of Label-Views to display a ScrollView with all the Labels. When those Labels are tapped, they are selected and an @State boolean property in the Label-View is toggled. So at the bottom when a "Submit" Button is clicked, the values of the selected Labels are sent to a server.

In the "Submit" Button I am using a For-Loop to loop over the Label-Views and check for Labels that have myLabel.isSelected == true, but all of them are returning false even after being selected.

The main View simplified:

struct Sheet: View {
    
    @State var myLabels: [MyLabel] = [
        MyLabel(systemImage: "sparkles", text: "Keine Gebrauchsspuren"),
        MyLabel(systemImage: "checkmark.circle", text: "Minimale Gebrauchsspuren"),
        MyLabel(systemImage: "exclamationmark.circle", text: "Mehrere Gebrauchsspuren"),
        MyLabel(systemImage: "xmark.circle", text: "Starke Gebrauchsspuren"),
        MyLabel(systemImage: "arrow.down.to.line", text: "Delle(n)")

    ]

    var body: some View {
        ScrollView {
              ForEach(myLabels, id: \.self) {label in label}
        }
        Button {
             var combinedString = ""
             for label in myLabels {
                   if label.isSelected {
                           combinedString  = label.text 
                   }
             }
             postTextFunc(combinedString)
        } label: {
             Text("Submit")
        }
    }

The combinedString is always returning empty because when I iterate over the myLabels all the Labels have isSelected set to false. Is this because of how structs are handled by SwiftUI when their properties are changed? And how would I be able to reach my goal of returning the values of only the tipped Labels?

Much appreciated!

CodePudding user response:

Views are structs in SwiftUI. So passing them into a function will create a copy of those Views. And you are itterating over the original Views that have not been modified.

I think a ViewModel/Model would be best here.

struct Model: Identifiable{
    var selected: Bool
    let text: String
    let id = UUID()
}

class Viewmodel: ObservableObject{
    @Published var models: [Model] = [] //Create the datamodel here
    
    func select(_ selectedModel: Model){
        var selectedModel = selectedModel
        selectedModel.selected.toggle() //Create a new copy and toggle selection
        
        // get index of selected item
        guard let index = models.firstIndex(where: {$0.id == selectedModel.id}) else{
            return
        }
        
        // assign item. This will toggle changes in the view
        models[index] = selectedModel
    }
    
func post(){
    //Create combined string
    let combinedString = models.filter{$0.selected}.reduce("") { partialResult, model in
        partialResult   model.text
    }
    //call post
    postTextFunc(combinedString)
}

func postTextFunc(_ string: String){
    
}
}

struct InnerView: View{
@EnvironmentObject private var viewmodel: Viewmodel
var selected: Model // provide the individual data here
var body: some View{
    Text(selected.text)
        .onTapGesture {
            viewmodel.select(selected)
        }
    }
}

struct TestView: View{
    
    @StateObject private var viewmodel = Viewmodel()
    
    var body: some View {
            ScrollView {
                ForEach(viewmodel.models, id: \.id) { model in
                    InnerView(selected: model)
                        .environmentObject(viewmodel) // pass viewmodel in environment
            }
        }
        Button {
            viewmodel.post()
        } label: {
             Text("Submit")
        }
    }
}
  • Related