You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何为比赛结果列表添加条件行号(含DNF处理)

问题:比赛结果列表行号添加(DNF选手不编号)的实现问题

我想给比赛结果列表添加行号,要求未完赛(DNF,通过dateTime == 999.999判断)的选手不显示行号,当前所有选手都显示为第1名。

我尝试用ObservableObject维护一个计数器,代码如下:

全局状态定义

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

列表调用代码

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

列表行实现

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).")
                .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))
  }
}

运行时出现编译错误:

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

请问这个实现方案是否可行?


解答

这个方案不可行,核心原因是违反了SwiftUI的声明式设计原则:

SwiftUI的View.body是用来描述UI结构的,不能在其中直接执行状态修改这类副作用代码(比如globalSettings.counter += 1)。每次视图刷新都会重新执行body中的代码,这种写法不仅会触发编译错误,还可能导致无限循环、计数混乱等不可预测的行为。

正确实现方式:提前计算行号

不需要全局计数器,而是提前为每个完赛选手计算好对应的行号,有两种常用方式:

方式1:遍历实时计算(适合小规模数据)

在父视图的ForEach中,实时统计当前选手之前的完赛数量作为行号:

Section {
    List {
        // 用enumerated()获取索引,同时遍历
        ForEach(Array(entrants.enumerated()), id: \.element.id) { index, entrant in
            // 统计从开头到当前索引中完赛的选手数量
            let rank = entrants[0...index].filter { $0.dateTime != 999.999 }.count
            ResultsCell(entrant: entrant, rank: rank)
        }
    }
}

修改ResultsCell,去掉EnvironmentObject,直接接收计算好的行号:

struct ResultsCell: View {
    let entrant: EntrantsDatabase1
    let rank: Int // 直接传入行号

    var body: some View {
        HStack {
            Spacer().frame(width: 5)
            if entrant.dateTime != 999.999 {
                Text("\(rank).")
                    .frame(width: 20, height: 40, alignment: .leading)
                    .bold()
            } else {
                Text("-")
                    .frame(width: 20, height: 40, alignment: .leading)
                    .bold()
            }
            // 剩余UI代码保持不变
            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))
    }
}
方式2:预处理数据(适合大规模数据,性能更优)

在ViewModel或父视图中提前遍历数据,为每个选手计算好行号,避免每次渲染重复计算:

// 在父视图/ViewModel中定义预处理后的数组
var rankedEntrants: [(entrant: EntrantsDatabase1, rank: Int)] {
    var currentRank = 0
    return entrants.map { entrant in
        if entrant.dateTime != 999.999 {
            currentRank += 1
            return (entrant, currentRank)
        } else {
            return (entrant, 0) // 0代表无行号
        }
    }
}

然后在列表中使用预处理后的数组:

Section {
    List {
        ForEach(rankedEntrants, id: \.entrant.id) { item in
            ResultsCell(entrant: item.entrant, rank: item.rank)
        }
    }
}

ResultsCell的修改和方式1一致。


内容的提问来源于stack exchange,提问作者Edward Hasted

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 23:39:54