fix: auto-scroll loads more when near bottom
onAppear doesn't fire during programmatic scrolling (UIScrollView contentOffset changes don't trigger SwiftUI lifecycle). Added onNearBottom callback to ScrollViewDriver — fires when within 500pt of bottom during auto-scroll tick. 3s cooldown prevents rapid-fire. Auto-scroll no longer stops at bottom — idles at maxOffset while loadMore fetches. When new entries arrive, contentSize grows and scrolling resumes automatically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,7 +40,9 @@ struct EntryListView: View {
|
|||||||
} else {
|
} else {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
// Auto-scroll engine — zero-size, drives parent UIScrollView
|
// Auto-scroll engine — zero-size, drives parent UIScrollView
|
||||||
ScrollViewDriver(isScrolling: $isAutoScrolling, speed: scrollSpeed)
|
ScrollViewDriver(isScrolling: $isAutoScrolling, speed: scrollSpeed) {
|
||||||
|
Task { await vm.loadMore() }
|
||||||
|
}
|
||||||
.frame(width: 0, height: 0)
|
.frame(width: 0, height: 0)
|
||||||
|
|
||||||
if isCardView {
|
if isCardView {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import UIKit
|
|||||||
struct ScrollViewDriver: UIViewRepresentable {
|
struct ScrollViewDriver: UIViewRepresentable {
|
||||||
@Binding var isScrolling: Bool
|
@Binding var isScrolling: Bool
|
||||||
let speed: Double // 1.0 = 60pt/sec
|
let speed: Double // 1.0 = 60pt/sec
|
||||||
|
var onNearBottom: (() -> Void)? = nil
|
||||||
|
|
||||||
func makeUIView(context: Context) -> UIView {
|
func makeUIView(context: Context) -> UIView {
|
||||||
let view = DriverView()
|
let view = DriverView()
|
||||||
@@ -21,6 +22,7 @@ struct ScrollViewDriver: UIViewRepresentable {
|
|||||||
let coordinator = context.coordinator
|
let coordinator = context.coordinator
|
||||||
coordinator.speed = speed
|
coordinator.speed = speed
|
||||||
coordinator.isScrollingBinding = $isScrolling
|
coordinator.isScrollingBinding = $isScrolling
|
||||||
|
coordinator.onNearBottom = onNearBottom
|
||||||
|
|
||||||
if isScrolling && coordinator.displayLink == nil {
|
if isScrolling && coordinator.displayLink == nil {
|
||||||
coordinator.startScrolling(in: driver)
|
coordinator.startScrolling(in: driver)
|
||||||
@@ -54,8 +56,10 @@ struct ScrollViewDriver: UIViewRepresentable {
|
|||||||
var displayLink: CADisplayLink?
|
var displayLink: CADisplayLink?
|
||||||
var speed: Double = 1.0
|
var speed: Double = 1.0
|
||||||
var isScrollingBinding: Binding<Bool>?
|
var isScrollingBinding: Binding<Bool>?
|
||||||
|
var onNearBottom: (() -> Void)?
|
||||||
private var originalDelegate: UIScrollViewDelegate?
|
private var originalDelegate: UIScrollViewDelegate?
|
||||||
private var delegateInstalled = false
|
private var delegateInstalled = false
|
||||||
|
private var loadMoreTriggered = false
|
||||||
|
|
||||||
func findScrollView(from view: UIView) {
|
func findScrollView(from view: UIView) {
|
||||||
var current: UIView? = view.superview
|
var current: UIView? = view.superview
|
||||||
@@ -110,10 +114,22 @@ struct ScrollViewDriver: UIViewRepresentable {
|
|||||||
|
|
||||||
originalDelegate?.scrollViewDidScroll?(sv)
|
originalDelegate?.scrollViewDidScroll?(sv)
|
||||||
|
|
||||||
if newY >= maxOffset - 1 {
|
// Trigger load more when within 500pt of bottom
|
||||||
stopAndNotify()
|
let distanceToBottom = maxOffset - newY
|
||||||
|
if distanceToBottom < 500 && !loadMoreTriggered {
|
||||||
|
loadMoreTriggered = true
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.onNearBottom?()
|
||||||
|
// Reset after a delay so it can trigger again for the next page
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
self?.loadMoreTriggered = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't stop at bottom — contentSize may grow after loadMore.
|
||||||
|
// The tick keeps running; if no more content, it just idles at maxOffset.
|
||||||
|
}
|
||||||
|
|
||||||
private func stopAndNotify() {
|
private func stopAndNotify() {
|
||||||
stopScrolling()
|
stopScrolling()
|
||||||
|
|||||||
Reference in New Issue
Block a user