From 1cfb729cae5058a31b1c884ca5ec9d1233239beb Mon Sep 17 00:00:00 2001 From: Yusuf Suleman Date: Sat, 4 Apr 2026 13:39:55 -0500 Subject: [PATCH] fix: auto-scroll loads more when near bottom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../Features/Reader/Views/EntryListView.swift | 6 ++++-- .../Reader/Views/ScrollViewDriver.swift | 20 +++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/ios/Platform/Platform/Features/Reader/Views/EntryListView.swift b/ios/Platform/Platform/Features/Reader/Views/EntryListView.swift index 3e6ff27..cba81bf 100644 --- a/ios/Platform/Platform/Features/Reader/Views/EntryListView.swift +++ b/ios/Platform/Platform/Features/Reader/Views/EntryListView.swift @@ -40,8 +40,10 @@ struct EntryListView: View { } else { ScrollView { // Auto-scroll engine — zero-size, drives parent UIScrollView - ScrollViewDriver(isScrolling: $isAutoScrolling, speed: scrollSpeed) - .frame(width: 0, height: 0) + ScrollViewDriver(isScrolling: $isAutoScrolling, speed: scrollSpeed) { + Task { await vm.loadMore() } + } + .frame(width: 0, height: 0) if isCardView { cardLayout diff --git a/ios/Platform/Platform/Features/Reader/Views/ScrollViewDriver.swift b/ios/Platform/Platform/Features/Reader/Views/ScrollViewDriver.swift index 6ea1102..88cf223 100644 --- a/ios/Platform/Platform/Features/Reader/Views/ScrollViewDriver.swift +++ b/ios/Platform/Platform/Features/Reader/Views/ScrollViewDriver.swift @@ -7,6 +7,7 @@ import UIKit struct ScrollViewDriver: UIViewRepresentable { @Binding var isScrolling: Bool let speed: Double // 1.0 = 60pt/sec + var onNearBottom: (() -> Void)? = nil func makeUIView(context: Context) -> UIView { let view = DriverView() @@ -21,6 +22,7 @@ struct ScrollViewDriver: UIViewRepresentable { let coordinator = context.coordinator coordinator.speed = speed coordinator.isScrollingBinding = $isScrolling + coordinator.onNearBottom = onNearBottom if isScrolling && coordinator.displayLink == nil { coordinator.startScrolling(in: driver) @@ -54,8 +56,10 @@ struct ScrollViewDriver: UIViewRepresentable { var displayLink: CADisplayLink? var speed: Double = 1.0 var isScrollingBinding: Binding? + var onNearBottom: (() -> Void)? private var originalDelegate: UIScrollViewDelegate? private var delegateInstalled = false + private var loadMoreTriggered = false func findScrollView(from view: UIView) { var current: UIView? = view.superview @@ -110,9 +114,21 @@ struct ScrollViewDriver: UIViewRepresentable { originalDelegate?.scrollViewDidScroll?(sv) - if newY >= maxOffset - 1 { - stopAndNotify() + // Trigger load more when within 500pt of bottom + 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() {