How do I initialize a view models variables in a view

I have a view that displays core data values (passed in as closingValues) in a data table. To make the view generic, so I can pass in arrays of different lengths (all are from the same entity and thus attributes) and account for the view having already been called with a different set of values, I need to initialize the showData array contained in the associated view model. I attempt to do so in the init of the view but I get an error message "publishing changes from within a view updates is not allowed. This will cause undefined behavior". It should be noted that in the view model showData is Published and initialized = []. I attempted to do the initialization in the views onappear but the tables rows: ForEach runs before onappear and I get an index out of range on the first call to the view. I tried adding @MainActor to the view models class, which also failed to solve the problem. I am clearly not understanding a concept. Below is the code for the view.

struct DataTable: View {
    @ObservedObject var vm: ButtonsViewModel = ButtonsViewModel.shared
    var closingValues: [TradingDayClose]
    var heading: String = ""
    init(fundName: String, closingValues: [TradingDayClose]) {
        self.heading = fundName
        self.closingValues = closingValues
        vm.showData = []
        vm.showData = Array(repeating: false, count: closingValues.count)
        // Publishing changes from within view updates is not allowed, this will cause undefined behavior.
    }
    var body: some View {
        HStack {
            Spacer()
                .frame(width: 150)
            GroupBox(heading) {
                Table (of: TradingDayClose.self) {
                    TableColumn("") { closingValue in
                        Text(dateToStringFormatter.string(from: closingValue.timeStamp!))
                            .id(closingValue.timeStamp!)
                            .textFormatting(fontSize: 14)
                            .frame(width: 100, alignment: .center)
                    } // end  table column
                    TableColumn("") { closingValue in
                        Text(String(format: "$ %.2f", closingValue.close))
                            .textFormatting(fontSize: 14)
                            .frame(width: 100, alignment: .center)
                    } // end  table column
                } rows: {
                    ForEach((closingValues.indices), id: \.self) { index in
                        if vm.showData[index] == true {
                            TableRow(closingValues[index])
                        }
                    }
                }
                .onAppear {
                    vm.InitializeIndexes()
                    vm.InitializeButtons()
                    for i in vm.startIndex...vm.endIndex {
                        DispatchQueue.main.asyncAfter(deadline: .now() + vm.renderRate * Double(i)) {
                            vm.showData[i] = true
                        } // end dispatch queue main async
                    }
                }
                .frame(width: 250)
                .overlay {
                    let tempValue1: String = "Date"
                    let tempValue2: String = "Closing Value"
                    Text(tempValue1).position(x: 63, y: 15)
                        .textFormatting(fontSize: 16)
                    Text(tempValue2).position(x: 180, y: 15)
                        .textFormatting(fontSize: 16)
                }
                
            } // end group box
            .groupBoxStyle(Table2GroupBoxStyle())
            Spacer()
                .frame(width: 50)
            VStack {
                Spacer()
                    .frame(height: 100)
                ButtonUp25(closingValuesCount: closingValues.count)
                ButtonUp200(closingValuesCount: closingValues.count)
                Spacer()
                    .frame(height: 20)
                ButtonDown25(closingValuesCount: closingValues.count)
                ButtonDown200(closingValuesCount: closingValues.count)
                Spacer()
                    .frame(height: 100)
            } // end v stack
            Spacer()
                .frame(width: 50)
        } // end h stack
    } // end body
} // end struct

Answered by Claude31 in 756296022

Doing in onAppear is a good option.

But apply onAppear to the global HStack that includes the Table, not the Table itself.

If that does not work, you could:

  • create a State var
@State private var hasAppeared = false
  • set it true in .onAppear
  • condition the display of Table with if hasAppeared
Accepted Answer

Doing in onAppear is a good option.

But apply onAppear to the global HStack that includes the Table, not the Table itself.

If that does not work, you could:

  • create a State var
@State private var hasAppeared = false
  • set it true in .onAppear
  • condition the display of Table with if hasAppeared

I moved the on appear to the h stack but the table rows portion of the table still ran before the on appear resulting in an index out of range error. So I added the suggested state variable and if statement. The view now works perfectly. Thank you again for providing great advice. Below is the updated code.

struct DataTable: View {
    @ObservedObject var vm: ButtonsViewModel = ButtonsViewModel.shared
    var closingValues: [TradingDayClose]
    var heading: String = ""
    @State private var hasAppeared = false
    init(fundName: String, closingValues: [TradingDayClose]) {
        self.heading = fundName
        self.closingValues = closingValues
    }
    var body: some View {
        HStack {
            Spacer()
                .frame(width: 150)
            GroupBox(heading) {
                if hasAppeared {
                    Table (of: TradingDayClose.self) {
                        TableColumn("") { closingValue in
                            Text(dateToStringFormatter.string(from: closingValue.timeStamp!))
                                .id(closingValue.timeStamp!)
                                .textFormatting(fontSize: 14)
                                .frame(width: 100, alignment: .center)
                        } // end  table column
                        TableColumn("") { closingValue in
                            Text(String(format: "$ %.2f", closingValue.close))
                                .textFormatting(fontSize: 14)
                                .frame(width: 100, alignment: .center)
                        } // end  table column
                    } rows: {
                        ForEach((closingValues.indices), id: \.self) { index in
                            if vm.showData[index] == true {
                                TableRow(closingValues[index])
                            }
                        }
                    }
                    .frame(width: 250)
                    .overlay {
                        let tempValue1: String = "Date"
                        let tempValue2: String = "Closing Value"
                        Text(tempValue1).position(x: 63, y: 15)
                            .textFormatting(fontSize: 16)
                        Text(tempValue2).position(x: 180, y: 15)
                            .textFormatting(fontSize: 16)
                    }
                } // end has appeared
            } // end group box
            .groupBoxStyle(Table2GroupBoxStyle())
            Spacer()
                .frame(width: 50)
            VStack {
                Spacer()
                    .frame(height: 100)
                ButtonUp25(closingValuesCount: closingValues.count)
                ButtonUp200(closingValuesCount: closingValues.count)
                Spacer()
                    .frame(height: 20)
                ButtonDown25(closingValuesCount: closingValues.count)
                ButtonDown200(closingValuesCount: closingValues.count)
                Spacer()
                    .frame(height: 100)
            } // end v stack
            Spacer()
                .frame(width: 50)
        } // end h stack
        .onAppear {
            vm.showData = []
            vm.showData = Array(repeating: false, count: closingValues.count)
            vm.InitializeIndexes()
            vm.InitializeButtons()
            for i in vm.startIndex...vm.endIndex {
                DispatchQueue.main.asyncAfter(deadline: .now() + vm.renderRate * Double(i)) {
                    vm.showData[i] = true
                } // end dispatch queue main async
            }
            hasAppeared = true
        }
    } // end body
} // end struct
How do I initialize a view models variables in a view
 
 
Q