Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stuck-list-first-scroll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'zero-swift': patch
---

A `VirtualList` or `VirtualListView` stuck to the bottom (`stickToBottom`) no longer pulls the user's first scroll up back to the bottom. When the list tried to scroll past where it could, its next scroll, the user's, was taken for its own, and the list stayed stuck.
7 changes: 7 additions & 0 deletions swift/Sources/ZeroVirtual/VirtualizerCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -719,10 +719,17 @@ package final class VirtualizerCore<
@discardableResult
private func setScrollOffset(_ offset: Double) -> Bool {
guard let host, host.scrollOffset.rounded() != offset.rounded() else { return false }
let from = host.scrollOffset
programmaticScroll = true
writing = true
host.scroll(to: offset)
writing = false
guard host.scrollOffset != from else {
// Clamped to where it was: no scroll, and the user's next one isn't
// ours.
programmaticScroll = false
return false
}
if depth == 0, scrollDuringWrite {
scrollDuringWrite = false
onScroll()
Expand Down
21 changes: 21 additions & 0 deletions swift/Tests/ZeroVirtualTests/LayoutHostTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,27 @@ final class LayoutScrollView {
#expect(view.scrollOffset == offset)
}

@Test("stuck to the bottom of a list opened at its last row, a scroll up moves the rows by it")
func scrollUpFromTheBottom() {
let h = Harness(rowCount: 400, rowHeight: 80) {
$0.anchoring = .manual
$0.permalinkID = "r399"
$0.stickToBottom = true
$0.minPageSize = 20
}
let view = LayoutScrollView(h)
for i in 0..<400 { view.heights["r\(i)"] = [62.3, 180.6, 38.5, 121.0, 76.0][i % 5] }
view.settle()
h.scheduler.advance(by: 200)
view.settle()
#expect(view.bottomGap == 0)
// The core's scrolls past the bottom stay where they are: the user's
// next scroll is the user's, not taken back to the bottom.
let top = try! #require(view.rowTop("r396"))
view.userScroll(view.scrollOffset - 20.5)
#expect(view.rowTop("r396") == top + 20.5)
}

@Test("scrolls to a row not loaded yet")
func scrollToItemFarBelow() {
let h = Harness(rowCount: 200) { $0.anchoring = .manual }
Expand Down
Loading