0

Race Results

I want to add a row number to a race list as shown above. Currently everyone is coming first. It's conditional as some people didn't finish (DNF).

So first I set up an ObservableObject:

class GlobalSettings: ObservableObject {
    @Published var counter: Int = 0
}

The structure that display each row is called from:

Section {
    List {
        ForEach(entrants) { entrant in
            ResultsCell(entrant: entrant)
        }
    }
}

The row is displayed by ResultsCell:

struct ResultsCell: View {
let entrant: EntrantsDatabase1
@EnvironmentObject var globalSettings: GlobalSettings
 
var body: some View {
    HStack {
        Spacer()
            .frame(width: 5)
        if entrant.dateTime != 999.999 {
             globalSettings.counter += 1
             Text("\(globalSettings.counter).")
            // Text("1.")
                .frame(width: 20, height: 40, alignment: .leading)
                .bold()
        } else {
            Text("-")
                .frame(width: 20, height: 40, alignment: .leading)
                .bold()
        }
        Spacer()
            .frame(width: 5)
        CircledText(text: String(entrant.number))
            .frame(width: 48, height: 40, alignment: .leading)
            .bold()
        Spacer()
            .frame(width: 10)
        Text(entrant.name)
            .frame(width: 160, height: 40, alignment: .leading)
            .bold()
        Spacer()
            .frame(width: 10)
        if entrant.dateTime != 999.999 {
            Text("\(entrant.dateTime, specifier: "%.3f")")
                .frame(width: 70, height: 40, alignment: .trailing)
                .bold()
        } else {
            Text("DNF")
                .frame(width: 70, height: 40, alignment: .trailing)
                .bold()
        }
    }
    .listRowBackground(entrant.dateTime == 999.999 ? Color.red: Color(.tertiarySystemBackground))
  }
}

The row counter only increments if the person finishes. Using this approach I get a

'buildExpression' is unavailable: this expression does not conform to 'View'

error when I try and use the globalSettings.counter variable.

Is this approach valid/Workable?

4
  • I wonder why you wouldn't modify globalSettings.counter within the parent view (the one that held the Section). Because at the time List renders, you already know how many dateTime that != 999.999. Commented Jun 5 at 8:30
  • The reason you get the error is because in a SwiftUI View you cannot have "normal" procedural code like globalSettings.counter += 1. This type of code needs to be in special places (eg .onAppear{...}, Button action etc...) or in functions. Commented Jun 5 at 8:33
  • But if I add the conditional testing into the section I get the same error. I have checked for this all over the place. I am aware that all examples cite buttons as the way to do the increment but I need to do it programatically. How? 'code' Section { List { ForEach(entrants) { entrant in if entrant.dateTime != 999.999 { globalSettings.counter += 1 } ResultsCell(entrant: entrant) } } }'code' Commented Jun 5 at 9:32
  • yes, when you add your condition to the Section you get the same error, because as I said before, in SwiftUI View you cannot have "normal" procedural code like that in the body, only other View. There are many SO posts on this subject, search for them. Commented Jun 5 at 9:43

2 Answers 2

1

You don't need the GlobalSettings. Try this approach using ForEach(Array(entrants.enumerated()),... as shown in the example code

Note, comparing two Doubles in your if entrant.dateTime != 999.999 etc... can lead to problems due to floating-point errors.


struct ContentView: View {
  
    // for testing
    let entrants = [
        EntrantsDatabase(id: 1, number: "1", dateTime: 1.3, name: "entrant-1"),
        EntrantsDatabase(id: 2, number: "2", dateTime: 45.6, name: "entrant-2"),
        EntrantsDatabase(id: 3, number: "3", dateTime: 999.999, name: "entrant-3")
    ]
    
    var body: some View {
        VStack {
            Section {
                List {
                    ForEach(Array(entrants.enumerated()), id: \.1.id) { index, entrant in  // <--- here
                        ResultsCell(index: index, entrant: entrant) // <--- here
                    }
                }
            }
        }
        .padding()
    }
}

// for testing
struct EntrantsDatabase: Identifiable {
    var id: Int  // <--- important
    var number: String
    var dateTime: Double
    var name: String
}

struct ResultsCell: View {
    let index: Int // <--- here
    let entrant: EntrantsDatabase

    var body: some View {
        HStack {
            Spacer()
                .frame(width: 5)
            if entrant.dateTime != 999.999 {
                Text("\(index).").foregroundStyle(.blue)  // <--- here
                    .frame(width: 20, height: 40, alignment: .leading)
                    .bold()
            } else {
                Text("-")
                    .frame(width: 20, height: 40, alignment: .leading)
                    .bold()
            }
            Spacer()
                .frame(width: 5)
       //     CircledText(text: String(entrant.number))
            Text(String(entrant.number)) // <--- for my testing
                .frame(width: 48, height: 40, alignment: .leading)
                .bold()
            Spacer()
                .frame(width: 10)
            Text(entrant.name)
                .frame(width: 160, height: 40, alignment: .leading)
                .bold()
            Spacer()
                .frame(width: 10)
            if entrant.dateTime != 999.999 {
                Text("\(entrant.dateTime, specifier: "%.3f")")
                    .frame(width: 70, height: 40, alignment: .trailing)
                    .bold()
            } else {
                Text("DNF")
                    .frame(width: 70, height: 40, alignment: .trailing)
                    .bold()
            }
        }
        .listRowBackground(entrant.dateTime == 999.999 ? Color.red: Color(.tertiarySystemBackground))
    }
}

Sign up to request clarification or add additional context in comments.

Comments

0

It's best to calculate the race result positions once, i.e. when the race ends and is saved, not every time it is being displayed.

This approach is so common that core data even has a way to do it automatically - derived attributes, that get recalculated on save.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.