UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Odd behavior passing data between parent and child views

Forums > SwiftUI

I have a ForEach iterating over 3 sets of data, inside the foreach there is a navigation link which passes @State variables to @Binding variables in the child view. The child view accepts data, processes it and passes it back to the parent view - all successfully. If you navigate to each of the three items from top to bottom, the information input on the child view from the first item still appears in the child view for the two subsequent items. If changed it will update the correct item in core data, and pass that back to the parent and show in the correct item, but which ever item you. navigate to subsequently the data from the first item in the set is loaded into the child view.

It gets weirder-- If I start with the third item in the view and work upwords, it works more as expected, when you move to the second item, with the data in the child view set to the initial values in the second item, and not to whatever was entered in the third item.

I tried clearning the binding variables in the child view before returning to the parent view, but that doesn’t seem to help.

Apologize for the lengthy code, but couldn't figure out how to demonstrate the problem in a shorter way.

Here are the screen shots of the parent and child views. https://www.dupscore.com/dupscore-v2-2


import Foundation
import SwiftUI

class Flag: ObservableObject {
        @Published var showFlag = false
    }
struct ScoreInputView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @FetchRequest(entity: Match.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Match.date, ascending: true)], animation: .default)
    var matches: FetchedResults<Match>
    @Binding var tabSelection: Int
    var match:Match
    @StateObject var showFlag = Flag()
    @State var boardNos = 1
    @State var suitx = ["??","♣️", "♦️", "♥️", "♠️", "🚫"]
    @State var bidNet = 0
    @State var bidLevel = "0"
    @State var bidSuit = "0"
    @State var bidBy  = "?"
    @State var bidDbl = "N"
    @State var bidDown = "0"
    @State var bidMade = "0"
    @State var boardNo = "1"
    @State var bidVul = " "
    @State var nsScore = Int16(0)
    @State var ewScore = Int16(0)
    @State var lead = "xC"
    @State var nsPts = 0.0
    @State var ewPts = 0.0
    // var tables = [1,2,3]
    var tables = [Int]()
    //MARK:  = B O D Y ============================================================
    var body: some View {
        let scoreArray = match.scoreArray
        let totalTables = match.maxPlayers/4
        ZStack {
        CustomBackView()
        NavigationView {
        VStack (alignment: .leading) {
            //MARK: Select Board Number
            HStack {
                Text("Board \(boardNos)   ").boardStyle()
                Stepper( "", value: $boardNos, in: 1...(Int(match.maxBoards)))
            }.padding([ .leading, .trailing], 50)
                .padding([.top, .bottom],10)
                .border(Color.green, width: 3)
                .background(Color.green)
                .foregroundColor(Color.white)
                .font(.title2)
            Divider()
                let tables = ScoreInputView.setTables(tt: Int(totalTables))
            List { 
        The ForEach code is here ...........
        ```
                //MARK: =========== FOR EACH TABLE t ===================
                ForEach(tables, id:\.self) { t  in
                    let score = getScores(boardnos: boardNos, tt: Int(totalTables), scoreArray: scoreArray, t: t)
                    VStack { //Player Stack
                        let nsew = score.nsew.split(separator: ",")
                        HStack (){
                            Text("Table ").bold()
                            Text(t.description).bold()
                            Spacer()
                        }//Table Number
                        HStack {
                        VStack {
                        HStack {
                            Text("N") // direction
                            Text(nsew[0].description) // player number
                            let n = (Int(nsew[0].description) ?? 1) - 1
                            Text((match.players?[n])!)// name
                        }
                        HStack {
                            Text("S")
                            Text(nsew[1].description)
                            let s  = (Int(nsew[1].description) ?? 1) - 1
                            Text((match.players?[s])!)
                        }
                        Divider()
                        HStack {
                            Text("E")
                            Text(nsew[2].description)
                            let e = (Int(nsew[2].description) ?? 1) - 1
                            Text((match.players?[e])!)
                        }
                        HStack {
                            Text("W")
                            Text(nsew[3].description)
                            let w = (Int(nsew[3].description) ?? 1) - 1
                            Text((match.players?[w])!)
                        }
                            } // Stack of Player Names
                        Divider()
                            //MARK: =========   Score Box  ==============
                        VStack {
                            let score = getScores(boardnos: boardNos, tt: Int(totalTables), scoreArray: scoreArray, t: t)

                            // Box with the bid results
                            HStack {
                                Text(score.bidLevel ?? "0")
                                Text(suitx[Int(score.bidSuit ?? "??") ?? 0])
                                Text(" by ")
                                Text(score.bidBy ?? "N" ).font(Font.custom("Avenir Black", size: 18))
                                Text(score.bidDbl ?? "N" )
                                Text(score.bidVul ?? " " )
                            }
                            HStack {
                                Text("+")
                                Text("-")
                                Text("NS  EW" )
                                Text("NS  EW")
                            }
                            HStack {
                                Text(score.bidMade  ?? "0")
                                    .foregroundColor(Color.green)
                                    .font(.system( size: 16, weight: .medium))
                                Text(score.bidDown ?? "0").foregroundColor(Color.red);
                                Text(String(score.nsScore))
                                Text(String(score.ewScore))
                                Text(String(score.nsPts))
                                Text(String(score.ewPts))
                            }
                        } // scored bid box of 2 hstacks
                        .padding(0)
                        .frame(width: 175)
                          //  .border(Color.red, width: 3)
                        } // end of HStack with Scored Board
                        ```
                        Navigation Link  Here ........

                        ```
                        NavigationLink(
                            destination: BidPicker( matches: _matches, boardNos: boardNos,t: t  ,match: match, bidNet: $bidNet,  bidLevel: $bidLevel, bidSuit: $bidSuit,  bidBy: $bidBy, bidDbl: $bidDbl, bidMade: $bidMade, bidDown: $bidDown, bidVul: $bidVul, showFlag: showFlag),
                            label: { }
                        )  // end Nav Link
                        .navigationTitle("Boards")
                        .navigationBarHidden(true)
                    }// HStacks with  Players and Scored Bid
                } //.padding([.leading],0) // End ForEach
                Divider()
            }// End List
                .listStyle()
        //====================    Buttons    ======================
        HStack { // HStack with form controls
            Button("Dismiss") {
            action: do {
              //  preProcessBoardSet(boardno: boardNos)//Mark: Process Set
                self.presentationMode.wrappedValue.dismiss() }
                    } .buttonStyle(GreenButton())
        }// Stack with Buttons
    }
    } // End  First VStack
    } // End ZStack
    } // End View Body  **

//MARK: ============ F U N C T I O N S ========================== //MARK: ========================================================== static func setTables (tt: Int) -> [Int] { var tables = [1] switch tt{ case 2: tables = [1,2] case 3: tables = [1,2,3] case 4: tables = [1,2,3,4] default: tables = [1,2,3,4,5] } return tables}

func getScores (boardnos: Int, tt: Int, scoreArray: [Score], t: Int) -> Score {
    var   boardIndex = (boardnos-1) * tt + t - 1
    if boardIndex < 0 {boardIndex = 1}
    let   score = scoreArray[boardIndex]
  //  print(boardIndex.description, t.description)
    bidLevel = score.bidLevel ?? "0"
    bidSuit = score.bidSuit  ?? "0"
    bidMade = score.bidMade  ?? "0"
    bidDown = score.bidDown  ?? "0"
    bidBy = score.bidBy  ?? "N"
    bidDbl = score.bidDbl ?? " "
    bidVul = score.bidVul  ?? " "
    nsPts = score.nsPts
    ewPts = score.ewPts
    return score }

}

1      

hi,

as someone who has an interest in bridge scoring programs (i have one that's oriented toward Chicago rather than duplicate), i'd like to help, but all of us on the Forum could use some help on your end as well. your code is a little difficult to follow.

first, i'd suggest some refactoring of the view into smaller views to help with readability.

example: instead of

 // Box with the bid results
   HStack {
    Text(score.bidLevel ?? "0")
    Text(suitx[Int(score.bidSuit ?? "??") ?? 0])
    Text(" by ")
    Text(score.bidBy ?? "N" ).font(Font.custom("Avenir Black", size: 18))
    Text(score.bidDbl ?? "N" )
    Text(score.bidVul ?? " " )
  }

this would work much better

 // Box with the bid results
   BidResultsBox(score: score)

where we define

struct BidResultsBox: View { // note: this is a View ... forgot that in the original post (!)

  var score: Score

 var body: some View {
     HStack {
      Text(score.bidLevel ?? "0")
      Text(suitx[Int(score.bidSuit ?? "??") ?? 0])
      Text(" by ")
      Text(score.bidBy ?? "N" ).font(Font.custom("Avenir Black", size: 18))
      Text(score.bidDbl ?? "N" )
      Text(score.bidVul ?? " " )
    }
  }

}

second, do you really need all of these @State variables? i think some readers of the Forum might see this and quickly move along to the next post.

on the other hand, if you really do need many of these, why not use a single @State private var score: Score, since it looks like just about all of the individual @State variables are fields of a Score object? even then, i don't know that you need it.

finally, i am puzzled by one construct above:

ForEach(tables, id:\.self) { t  in
  let score = getScores(boardnos: boardNos, tt: Int(totalTables), scoreArray: scoreArray, t: t)
  // lots of stuff to follow, mostly based on `score`
}

executing getScores() causes the code to update many @State variables, which should be making SwiftUI go a little crazy ... because executing the body property is causing the state to change underneath it as it executes. really, the body property should only read the values of @State variables. it's hard for SwiftUI to be asked to draw a representation of the data held in @State variabes if, as it starts to draw, you change the data that it was using.

on the other hand, since you really just want to read values of the scoreArray (or perhaps just some subset of the scoreArray), why not simply write something of the form

ForEach(scoreArray, id:\.self) { score  in
  // lots of stuff to follow, mostly based on `score`
}

(if each Score were Identifiable, you could skip the id: \.self qualifier above.)

i look forward to see where you go with this.

hope this helps,

DMG

1      

Hello DelawareMathGuy, thanks very much for responding. I am "hobby" programmer, I guess and doing on my own, so apologize for the less than tight code.

I get the refactoring, and will do that.

getScores is getting the information for a single board played at a single table. It's calculating the index into the array and moving that data to the "bid" fields which are passed to the child view where the bid info is entered and results are calculated.

Can't do a ForEach across the entire array because it contains score for all of the boards and I just need for each table on a single board.

Will think about whether I could just pass the score row as a whole to the bidCalc(child view) instead of the individual items or maybe just board and table number, thereby reducing the individual items needed to be state variables. Since the purpose of the parent view is just navigation and board selection, that might make more sense.

Not sure if the restructure will change the weird behavior, but I'll see.

Score core data class is identifiable.

The sequence of how swiftui triggers processing, or sees changes that trigger redraw is currently a challenge for me.

1      

hi Sandy,

for the record, i am also a "hobby programmer," only in the sense that i write code for things that interest me, and try to help out others. i enjoy bridge (albeit Chicago-style with my local group that can have anywhere from 4 to 10 players each week). and i am also retired.

so, keep going ...

just a few comments for you right now.

getScores is getting the information for a single board played at a single table. It's calculating the index into the array and moving that data to the "bid" fields which are passed to the child view where the bid info is entered and results are calculated.

this is probably not quite the right idea in SwiftUI. executing the body property and using a ForEach is not like a procedural programming and using a for score in scoreArray { } statement. you really do not want to mix these ideas; and, i would not be surprised if the behaviour you're seeing is directly related to this.

if you want to restrict the scores shown in the ForEach, you could use a computed property on the view, so perhaps write

ForEach(myTableScores, id:\.self) { score  in
  // lots of stuff to follow, mostly based on `score`
}

and then define the computed property myTableScores on the view with maybe

var myTableScores: [Score] {
  // just return an array of only those entries in the scoreArray that are of interest
  // e.g, if you only want entries in the scoreArray at indices 4, 5, 6, and 7, then you 
  // could use this
  [4, 5, 6, 7].map({ scoreArray[$0] }) // picks out only entries at indices indices 4, 5, 6, and 7
}

and which elements of the scoreArray you pick out can be determined by another variable, whether it be a @State variable or not (it looks like you have a commented-out var tables = [1,2,3] variable lurking in the view).

i also loved it when you stated what every reader of this Forum felt at some point:

The sequence of how swiftui triggers processing, or sees changes that trigger redraw is currently a challenge for me.

yes, the mysteries still continue ... surely next week's WWDC will clear this all up (!)

feel free to contact me directly, and i am happy to take a look at code if you wish ... you can find me on GitHub.

hope that helps,

DMG

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.