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

SwiftUI:滚动到ScrollView新列表项时更新状态的问题

问题:SwiftUI滚动时让datetimeID随每个列表项更新而非仅Section

我正在构建一个SwiftUI视图,展示按日期分组的垂直项目列表,需求是用户滚动到列表中的新项时更新状态变量datetimeID。当前实现中datetimeID仅在滚动到新Section时更新,因为.scrollTargetLayout()加在了LazyVStack上。尝试把修饰符加到Rectangle上但没生效,求解决办法。

当前代码:

struct ProductsListView: View {
  @State private var datetimeID: Date?
  
  var body: some View {
    ZStack {
      Color.blue
      
      let calendar = Calendar(identifier: .gregorian)
      let startDate = calendar.startOfDay(for: .now)
      let endDate = startDate.addingTimeInterval(60*60*24*70)
      let interval = 60.0 * 60.0
      
      let array = Array(stride(from: startDate, to: endDate, by: interval))
      let dates: [Date: [Date]] = Dictionary(
        grouping: array,
        by: { calendar.startOfDay(for: $0) }
      )
      
      VStack(alignment: .leading, spacing: 3) {
        Text("Products Gallery")
          .font(.largeTitle)
          .fontWeight(.heavy)
          .foregroundStyle(.white)
        
        Text("Log: \(datetimeID?.description ?? \"nil\")")
          .foregroundStyle(.white).bold().font(.headline)
        
        ScrollView(.vertical) {
          LazyVStack(alignment: .leading, spacing: 2, pinnedViews: [.sectionHeaders]) {
            ForEach(dates.keys.sorted(), id: \.self) { date in
              Section(header: Text("\(date.description)").opacity(0.4)) {
                ForEach(dates[date]?.sorted() ?? [], id: \.self) { time in
                  let components = calendar.dateComponents([.hour], from: time)
                  let hour = components.hour!
                  Rectangle()
                    .fill(hour % 3 == 0 ? .orange : .yellow)
                    .frame(width: .infinity, height: 20)
                }
              }
            }
          }
          .scrollTargetLayout()
        }
        .foregroundStyle(Color.white)
        .scrollTargetBehavior(.viewAligned)
        .scrollPosition(id: $datetimeID)
      }
      .padding()
    }
  }
}

解决方案

核心问题是LazyVStack作为滚动目标容器时,默认会把Section的id作为滚动目标标识。要让每个Rectangle成为独立的滚动目标,需做以下修改:

  • 给每个Rectangle绑定唯一id:在每个Rectangle视图上添加.id(time),这个id要和scrollPosition绑定的datetimeID类型一致(均为Date),确保系统能识别每个项的独立身份。
  • 保留容器上的.scrollTargetLayout():该修饰符必须放在包含所有滚动目标的容器(即LazyVStack)上,让系统能扫描容器内的所有子视图作为滚动目标候选。
  • 调整滚动目标行为(可选):如果.viewAligned仍不精准,可改用.scrollTargetBehavior(.paging),强制滚动时对齐到完整的视图项,更易触发id更新。

修改后的代码:

struct ProductsListView: View {
  @State private var datetimeID: Date?
  
  var body: some View {
    ZStack {
      Color.blue
      
      let calendar = Calendar(identifier: .gregorian)
      let startDate = calendar.startOfDay(for: .now)
      let endDate = startDate.addingTimeInterval(60*60*24*70)
      let interval = 60.0 * 60.0
      
      let array = Array(stride(from: startDate, to: endDate, by: interval))
      let dates: [Date: [Date]] = Dictionary(
        grouping: array,
        by: { calendar.startOfDay(for: $0) }
      )
      
      VStack(alignment: .leading, spacing: 3) {
        Text("Products Gallery")
          .font(.largeTitle)
          .fontWeight(.heavy)
          .foregroundStyle(.white)
        
        Text("Log: \(datetimeID?.description ?? \"nil\")")
          .foregroundStyle(.white).bold().font(.headline)
        
        ScrollView(.vertical) {
          LazyVStack(alignment: .leading, spacing: 2, pinnedViews: [.sectionHeaders]) {
            ForEach(dates.keys.sorted(), id: \.self) { date in
              Section(header: Text("\(date.description)").opacity(0.4)) {
                ForEach(dates[date]?.sorted() ?? [], id: \.self) { time in
                  let components = calendar.dateComponents([.hour], from: time)
                  let hour = components.hour!
                  Rectangle()
                    .fill(hour % 3 == 0 ? .orange : .yellow)
                    .frame(width: .infinity, height: 20)
                    .id(time) // 给每个项绑定唯一id
                }
              }
            }
          }
          .scrollTargetLayout() // 保留在容器上
        }
        .foregroundStyle(Color.white)
        .scrollTargetBehavior(.viewAligned) // 或改用.paging
        .scrollPosition(id: $datetimeID)
      }
      .padding()
    }
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 02:23:13