feat: Connect SwiftUI ScrollView tracking to WebView coordinator
- Added WebViewCoordinator reference storage in BookmarkDetailLegacyView - Added updateScrollProgress() method to WebViewCoordinator with 3% threshold - Connected onScrollPhaseChange to coordinator's updateScrollProgress - Added onExternalScrollUpdate callback to pass coordinator reference - Scroll progress now flows: SwiftUI ScrollView -> Coordinator -> onScroll callback This bridges the gap between SwiftUI ScrollView (which wraps the WebView) and the JavaScript-style scroll progress tracking with threshold.
This commit is contained in:
parent
0a53705df1
commit
5c9c00134a
@ -12,9 +12,11 @@ struct BookmarkDetailLegacyView: View {
|
|||||||
@State private var showingFontSettings = false
|
@State private var showingFontSettings = false
|
||||||
@State private var showingLabelsSheet = false
|
@State private var showingLabelsSheet = false
|
||||||
@State private var readingProgress: Double = 0.0
|
@State private var readingProgress: Double = 0.0
|
||||||
|
@State private var currentScrollOffset: CGFloat = 0
|
||||||
@State private var showJumpToProgressButton: Bool = false
|
@State private var showJumpToProgressButton: Bool = false
|
||||||
@State private var scrollPosition = ScrollPosition(edge: .top)
|
@State private var scrollPosition = ScrollPosition(edge: .top)
|
||||||
@State private var showingImageViewer = false
|
@State private var showingImageViewer = false
|
||||||
|
@State private var webViewCoordinator: WebViewCoordinator? = nil
|
||||||
|
|
||||||
// MARK: - Envs
|
// MARK: - Envs
|
||||||
|
|
||||||
@ -60,6 +62,10 @@ struct BookmarkDetailLegacyView: View {
|
|||||||
print("🔥 BookmarkDetailLegacyView onScroll callback: \(String(format: "%.3f", scrollPercent)) (\(String(format: "%.1f", scrollPercent * 100))%)")
|
print("🔥 BookmarkDetailLegacyView onScroll callback: \(String(format: "%.3f", scrollPercent)) (\(String(format: "%.1f", scrollPercent * 100))%)")
|
||||||
readingProgress = scrollPercent
|
readingProgress = scrollPercent
|
||||||
viewModel.debouncedUpdateReadProgress(id: bookmarkId, progress: scrollPercent, anchor: nil)
|
viewModel.debouncedUpdateReadProgress(id: bookmarkId, progress: scrollPercent, anchor: nil)
|
||||||
|
},
|
||||||
|
onExternalScrollUpdate: { coordinator in
|
||||||
|
print("🎯 WebView coordinator ready, storing reference")
|
||||||
|
webViewCoordinator = coordinator
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.frame(height: webViewHeight)
|
.frame(height: webViewHeight)
|
||||||
@ -100,6 +106,24 @@ struct BookmarkDetailLegacyView: View {
|
|||||||
}
|
}
|
||||||
.ignoresSafeArea(edges: .top)
|
.ignoresSafeArea(edges: .top)
|
||||||
.scrollPosition($scrollPosition)
|
.scrollPosition($scrollPosition)
|
||||||
|
.onScrollGeometryChange(for: CGFloat.self) { geo in
|
||||||
|
geo.contentOffset.y
|
||||||
|
} action: { oldValue, newValue in
|
||||||
|
currentScrollOffset = newValue
|
||||||
|
}
|
||||||
|
.onScrollPhaseChange { oldPhase, newPhase in
|
||||||
|
// Only calculate when scrolling ends (interacting -> idle)
|
||||||
|
if oldPhase == .interacting && newPhase == .idle {
|
||||||
|
print("📊 Scroll ended at offset: \(currentScrollOffset)")
|
||||||
|
let offset = currentScrollOffset
|
||||||
|
let maxOffset = webViewHeight - geometry.size.height
|
||||||
|
|
||||||
|
print("🎯 Sending to WebView coordinator: offset=\(offset), maxOffset=\(maxOffset)")
|
||||||
|
|
||||||
|
// Send to WebView coordinator which handles the 3% threshold
|
||||||
|
webViewCoordinator?.updateScrollProgress(offset: offset, maxOffset: maxOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|||||||
@ -6,6 +6,7 @@ struct WebView: UIViewRepresentable {
|
|||||||
let settings: Settings
|
let settings: Settings
|
||||||
let onHeightChange: (CGFloat) -> Void
|
let onHeightChange: (CGFloat) -> Void
|
||||||
var onScroll: ((Double) -> Void)? = nil
|
var onScroll: ((Double) -> Void)? = nil
|
||||||
|
var onExternalScrollUpdate: ((WebViewCoordinator) -> Void)? = nil
|
||||||
@Environment(\.colorScheme) private var colorScheme
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
|
|
||||||
func makeUIView(context: Context) -> WKWebView {
|
func makeUIView(context: Context) -> WKWebView {
|
||||||
@ -21,6 +22,8 @@ struct WebView: UIViewRepresentable {
|
|||||||
webView.scrollView.isScrollEnabled = false
|
webView.scrollView.isScrollEnabled = false
|
||||||
webView.isOpaque = false
|
webView.isOpaque = false
|
||||||
webView.backgroundColor = UIColor.clear
|
webView.backgroundColor = UIColor.clear
|
||||||
|
|
||||||
|
print("🟢 WebView created with scrolling DISABLED (embedded in ScrollView)")
|
||||||
|
|
||||||
// Allow text selection and copying
|
// Allow text selection and copying
|
||||||
webView.allowsBackForwardNavigationGestures = false
|
webView.allowsBackForwardNavigationGestures = false
|
||||||
@ -30,7 +33,11 @@ struct WebView: UIViewRepresentable {
|
|||||||
webView.configuration.userContentController.add(context.coordinator, name: "scrollProgress")
|
webView.configuration.userContentController.add(context.coordinator, name: "scrollProgress")
|
||||||
context.coordinator.onHeightChange = onHeightChange
|
context.coordinator.onHeightChange = onHeightChange
|
||||||
context.coordinator.onScroll = onScroll
|
context.coordinator.onScroll = onScroll
|
||||||
|
context.coordinator.webView = webView
|
||||||
|
|
||||||
|
// Notify parent that coordinator is ready
|
||||||
|
onExternalScrollUpdate?(context.coordinator)
|
||||||
|
|
||||||
return webView
|
return webView
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,18 +350,22 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler
|
|||||||
// Callbacks
|
// Callbacks
|
||||||
var onHeightChange: ((CGFloat) -> Void)?
|
var onHeightChange: ((CGFloat) -> Void)?
|
||||||
var onScroll: ((Double) -> Void)?
|
var onScroll: ((Double) -> Void)?
|
||||||
|
|
||||||
|
// WebView reference
|
||||||
|
weak var webView: WKWebView?
|
||||||
|
|
||||||
// Height management
|
// Height management
|
||||||
var lastHeight: CGFloat = 0
|
var lastHeight: CGFloat = 0
|
||||||
var pendingHeight: CGFloat = 0
|
var pendingHeight: CGFloat = 0
|
||||||
var heightUpdateTimer: Timer?
|
var heightUpdateTimer: Timer?
|
||||||
|
|
||||||
// Scroll management
|
// Scroll management
|
||||||
var isScrolling: Bool = false
|
var isScrolling: Bool = false
|
||||||
var scrollVelocity: Double = 0
|
var scrollVelocity: Double = 0
|
||||||
var lastScrollTime: Date = Date()
|
var lastScrollTime: Date = Date()
|
||||||
var scrollEndTimer: Timer?
|
var scrollEndTimer: Timer?
|
||||||
|
var lastSentProgress: Double = 0
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
private var isCleanedUp = false
|
private var isCleanedUp = false
|
||||||
|
|
||||||
@ -450,11 +461,28 @@ class WebViewCoordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler
|
|||||||
if heightDifference < 5 { // Ignore tiny height changes that cause flicker
|
if heightDifference < 5 { // Ignore tiny height changes that cause flicker
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lastHeight = height
|
lastHeight = height
|
||||||
onHeightChange?(height)
|
onHeightChange?(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method to receive scroll updates from SwiftUI ScrollView
|
||||||
|
func updateScrollProgress(offset: CGFloat, maxOffset: CGFloat) {
|
||||||
|
let progress = maxOffset > 0 ? min(max(offset / maxOffset, 0), 1) : 0
|
||||||
|
|
||||||
|
print("📊 External scroll update: offset=\(offset), maxOffset=\(maxOffset), progress=\(String(format: "%.3f", progress))")
|
||||||
|
|
||||||
|
// Only send if change >= 3%
|
||||||
|
let threshold: Double = 0.03
|
||||||
|
if abs(progress - lastSentProgress) >= threshold {
|
||||||
|
print("✅ Calling onScroll callback with: \(String(format: "%.3f", progress))")
|
||||||
|
lastSentProgress = progress
|
||||||
|
onScroll?(progress)
|
||||||
|
} else {
|
||||||
|
print("⏸️ Skipping (change < 3%): \(String(format: "%.3f", abs(progress - lastSentProgress)))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func cleanup() {
|
func cleanup() {
|
||||||
guard !isCleanedUp else { return }
|
guard !isCleanedUp else { return }
|
||||||
isCleanedUp = true
|
isCleanedUp = true
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user