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

SwiftUI中ScrollView搭配LazyVGrid快速滚动时计时器冻结如何解决

问题根本原因

这个问题是SwiftUI 自带的 Text(.timer) 样式与Lazy容器的cell复用机制冲突导致的:快速滚动时,离屏的cell被LazyVGrid回收复用,系统对timer类型Text的刷新调度没有正确绑定到复用后的cell上,就会出现更新冻结的情况,只有再次触发cell渲染(滚动让cell再次出现)才会恢复。


可行解决方案

方案1:优先推荐,自定义定时器实现计时逻辑

放弃系统自带的timer样式Text,自己控制计时更新,不受系统调度限制,稳定性最高。
修改代码如下:

import Combine

struct CellView: View {
    let date: Date
    let categoryIndex: Int
    let rowIndex: Int
    // 新增计时相关属性
    @State private var elapsedTime: TimeInterval = 0
    private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    // 静态格式化工具避免重复创建
    private static let timeFormatter: DateComponentsFormatter = {
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = [.hour, .minute, .second]
        formatter.zeroFormattingBehavior = .pad
        return formatter
    }()
    
    init(date: Date, categoryIndex: Int, rowIndex: Int) {
        self.date = date
        self.categoryIndex = categoryIndex
        self.rowIndex = rowIndex
    }
    
    var body: some View {
        GeometryReader { geo in
            VStack() {
                Text("\(categoryIndex) -> \(rowIndex)")
                if((self.categoryIndex==0 || self.categoryIndex==2) && self.rowIndex<=4) {
                    // 替换系统timer样式为自定义格式化文本
                    Text(Self.timeFormatter.string(from: elapsedTime) ?? "00:00:00")
                    Text(date.formatDate("HH:mm:ss"))
                }
            }
            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            .background(Color.gray.opacity(0.2))
            .onAppear {
                // 初始计算当前和零点的时间差
                elapsedTime = Date().timeIntervalSince(date.getZeroData())
            }
            .onReceive(timer) { _ in
                // 每秒更新计时
                elapsedTime = Date().timeIntervalSince(date.getZeroData())
            }
        }
    }
}

同时建议优化Date扩展里的DateFormatter为静态实例,避免每次调用重复创建,提升性能。


方案2:修改成本最低,强制cell唯一标识

如果不想改动计时逻辑,只需要给Cell添加唯一的id标识,让LazyVGrid不混用不同位置的cell,就能解决大部分场景的冻结问题:

CellView(date: date, categoryIndex: categoryIndex, rowIndex: rowIndex)
    .frame(width: widgetWidth, height: widgetHeight, alignment: .center)
    .compositingGroup()
    .cornerRadius(10)
    .shadow(color: .black.opacity(0.05), radius: 6)
    // 新增唯一id即可
    .id("cell_\(categoryIndex)_\(rowIndex)")

方案3:优化List样式实现需求

List的样式自定义并不复杂,只需要添加几个修饰符就能达到和ScrollView+LazyVGrid一致的效果,同时复用调度更稳定:

List {
    // 原有的Section、LazyVGrid内容逻辑保持不变
}
.listStyle(.plain) // 去掉默认分组样式
.listRowSeparator(.hidden) // 隐藏分割线
.listRowInsets(EdgeInsets()) // 去掉行内边距
.environment(\.defaultMinListRowHeight, 0) // 去掉默认最小行高限制

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 03:27:03