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
相关产品推荐
相关产品推荐

