iosswiftswiftuiscrollviewscroll-position

SwiftUI: Updating State When Scrolling to a New Item in ScrollView


I’m building a SwiftUI view that displays a vertical list of items grouped by date. I want to update the state variable datetimeID whenever the user scrolls to a new item in the list. Here’s my current implementation:

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

The datetimeID is updating when the user scrolls to a new section, which is expected because .scrollTargetLayout() modifier is added to LazyVStack. But I want it update when the user scrolls to a new item (Rectangle in the code), so I’ve tried adding it to Rectangle() but didn't work.

Any help or suggestions would be greatly appreciated.


Solution

  • As suggested by Sweeper in the comment, adding anchor parameter solves the problem.

    .scrollPosition(id: $datetimeID, anchor: .top)
    

    Thanks Sweeper !