swiftuistructobservable

How to display a conditional row number in a list


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?


Solution

  • 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))
        }
    }