0

I have a view that outputs rows of data using ForEach. The data is an array of defined data records from an FMDB database. I use List / ForEach and LazyVGrid to populate the rows in the view.

My goal is to take two of the output fields in the LazyVGrid and use onGesture to invoke one of two .sheet views for the details behind the value.

In the onGesture I want to capture the dataRecord used to populate that row and use the data in that record to call the view in .sheet

It all seems to work until I get to the .sheet(isPresented... There the @State variable I populate in the onGesture is Nil

I'm putting in the code I use along with some print output that seems to show that the @State variable is populated in onGesture, but later in the .sheet(isPresented it is nil

I'm not sure I understand the scope of visibility for that @State variable and wonder if someone can help me figure this out...

MY CODE IS:


import SwiftUI

struct BudgetedIncomeView: View {
    
    @State var account_code:  Int
    @State var budgetYear: Int
    @State var budgetMonth: Int
    
    @State private var isAdding = false
    @State private var isEditing = false
    @State private var isDeleting = false

    @State private var budgetRec = BudgetedIncome.BudgetedIncomeRecord()
    
    @State private var isBudgeted = false
    @State private var isReceived = false
    
    // Environment and ObservedObjects
    @Environment(\.presentationMode) var presentationMode
    @Environment(\.editMode) var editMode
    
    @StateObject var toolbarViewModel = ToolbarViewModel()
    
    var displayMonthYearString: String {
        return calendarMonths(budgetMonth) + "-" + String(budgetYear)
    }
    
    let columns = [
        GridItem(.flexible(), alignment: .leading),
        GridItem(.fixed(UIScreen.main.bounds.width * 0.20), alignment: .trailing),
        GridItem(.fixed(UIScreen.main.bounds.width * 0.20), alignment: .trailing)
    ]
    
    let numberColor = Color.black
    let negativeColor = Color.red
    
    // Array of Budgeted Income records for View List
    var budgetedIncome: [BudgetedIncome.BudgetedIncomeRecord] {
        return BudgetedIncome.shared.selectBudgetedIncomeForAccountWithMonthAndYear(withAccountCode: self.account_code, withYear: self.budgetYear, withMonth: self.budgetMonth)
    }
    
    var body: some View {
        
        ZStack {
           
            VStack    {
                BudgetedIncomeHeader(headerText: "Budgeted")
                    .padding(.top, 10)
                List {
                    ForEach (self.budgetedIncome, id: \.self) { budgetRecord in

                        LazyVGrid(columns: columns, spacing: 5) {
                            Text("\(budgetRecord.description)")
                                .lineLimit(1)
                                .padding(.leading, 5)

                            Text("\(NumberFormatter.formatWithComma(value: budgetRecord.income_budget))")
                                .foregroundColor((budgetRecord.income_budget < 0 ) ? self.negativeColor : self.numberColor)
                                .onTapGesture {
                                    //
                                    // PRINT STATEMENTS THAT SHOW THE DATA IS CAPTURED
                                    //
                                    let _ = print("budgetRecord in onGesture = \(budgetRecord)\n\n")
                                    budgetRec = budgetRecord
                                    let _ = print("budgetRec in onGesture = \(budgetRec)\n\n")
                                    
                                    isBudgeted.toggle()
                                }


                            Text("\(NumberFormatter.formatWithComma(value: budgetRecord.income_received))")
                                .underline()
                                .padding(.trailing, 15)

                        }// END OF LAZYVGRID

                    }  // END OF FOREACH
                    
                }  // END OF LIST
                
                BudgetedIncomeFooter(accountCode: $account_code, budgetYear: $budgetYear, budgetMonth: $budgetMonth)
                    .padding(.top, 5)
                
            } // END OF VSTACK
            .sheet(isPresented: $isBudgeted ){
                //
                // PRINT STATEMENT THAT SHOWS THE DATA IS NIL HERE
                //
                let _ = print("budgetRec in .sheet = \(budgetRec)\n\n")
                
                BudgetedIncomeDetailsView(accountCode: budgetRec.account_code, incomeCode: budgetRec.income_code, budgetYear: budgetRec.budget_year, budgetMonth: budgetRec.budget_month)
                    .opacity(isBudgeted ? 1 : 0)
                    .zIndex(isBudgeted ? 1 : 0)
            }
            if isReceived {
                BudgetedIncomeDetailsView(accountCode: 12345678, incomeCode: 50060, budgetYear: 2020, budgetMonth: 12)
                    .opacity(isReceived ? 1 : 0)
                    .zIndex(isReceived ? 1 : 0)
            }
        } // END OF ZSTACK
        .navigationTitle("Budgeted Income for \(self.displayMonthYearString)")
        .navigationBarBackButtonHidden(true)
        .toolbar {
            ToolBarCancelDeleteAdd() {
                toolbarViewModel.cancelContent(editMode: editMode)
                presentationMode.wrappedValue.dismiss()
            }
            adding: {
                toolbarViewModel.addingContent(isAdding: &isAdding, editMode: editMode)
            }
        } // END OF TOOLBAR
        
    } // END OF BODY VIEW
    
} // END OF STRUCT VIEW

struct BudgetedIncomeView_Previews: PreviewProvider {
    static var previews: some View {
        BudgetedIncomeView(account_code: 12345678,
                           budgetYear: 2020,
                           budgetMonth: 12)
            .environmentObject(ApplicationSettings())
            .environmentObject(Budget())
            .environmentObject(GlobalSettings())
    }
}


The output from these print statements is:

budgetRecord in onGesture = BudgetedIncomeRecord(income_id: Optional(589), account_code: Optional(12345678), income_code: Optional(50060), budget_year: Optional(2020), budget_month: Optional(12), description: Optional("ADD BACK SET ASIDE"), category: Optional("*Exclude From Reports"), income_budget: Optional(3600.0), income_received: Optional(3600.0), unexpected_income: Optional(0.0), category_code: Optional(99999), set_aside: Optional(true), set_aside_id: nil)

budgetRec in onGesture = BudgetedIncomeRecord(income_id: Optional(589), account_code: Optional(12345678), income_code: Optional(50060), budget_year: Optional(2020), budget_month: Optional(12), description: Optional("ADD BACK SET ASIDE"), category: Optional("*Exclude From Reports"), income_budget: Optional(3600.0), income_received: Optional(3600.0), unexpected_income: Optional(0.0), category_code: Optional(99999), set_aside: Optional(true), set_aside_id: nil)

budgetRec in .sheet = BudgetedIncomeRecord(income_id: nil, account_code: nil, income_code: nil, budget_year: nil, budget_month: nil, description: nil, category: nil, income_budget: nil, income_received: nil, unexpected_income: nil, category_code: nil, set_aside: nil, set_aside_id: nil)

It seems to me that somewhere the data goes nil. I don't seem to follow where the data is going out of scope???

I have used this technique in other views where I have used a view for the row and let the whole row be selected .onGesture.

I haven't tried it using LazyVGrid and selecting specific output values..

Any help would be greatly appreciated..

Bob

rpetruzz
  • 107
  • 1
  • 10
  • 2
    Does this answer your question? [SwiftUI sheet shows sheet with wrong data](https://stackoverflow.com/questions/63426540/swiftui-sheet-shows-sheet-with-wrong-data) – lorem ipsum Jun 27 '21 at 22:46
  • While it points to a solution to invoking the view called from the .sheet, it doesn't really address for me the issue of why the value of "budgetRec" seems to exist in the onGesture based on the print output, but then later in the .sheet it has nil values. The crux of my question is to understand the question of why it is nil. – rpetruzz Jun 27 '21 at 23:17
  • 1
    [This question](https://stackoverflow.com/questions/65281559/swiftui-understanding-sheet-fullscreencover-lifecycle-when-using-constant-v/65281850#65281850) breaks it down a little more. if you use `let _ = print("budgetRec in .sheet = \($budgetRec.projectedValue)\n\n")` you should see the change. In short showing the sheet prevents the parent `body` from reloaded with the new value the `nil` comes from the value at the time of the latest reload – lorem ipsum Jun 27 '21 at 23:23
  • Yes!!! That clarifies it for me. I had to take the examples in that other question and run them to understand what was happening. What I learned and still puzzles me is that there appears to be multiple copies of the @State variable based on the view/subviews. I set it in the ForEach view and it isn't really visible in the parent VStack view until there is a refresh. That is different from other programming languages I've learned in the past where there memory for the variable was the same. I understand that Binding seems to solve that. It's interesting.. – rpetruzz Jun 28 '21 at 14:45
  • `@Binding` Would make the connection. By definition it is a two-way connection. `@State` Is storage/a source of truth. My assumption is that by blocking the refresh in the parent View the app prevents unnecessary refreshes and potential bugs with the showing of the sheet – lorem ipsum Jun 28 '21 at 15:07
  • Thanks again for the insights. I really increased my understanding of this now. – rpetruzz Jun 28 '21 at 18:10

0 Answers0