perf: Apply PreferenceKey optimization to BookmarkDetailView2 and fix spacing
- Implemented ScrollOffsetPreferenceKey for BookmarkDetailView2 - Replaced onScrollGeometryChange + onScrollPhaseChange with onPreferenceChange - Removed currentScrollOffset and scrollViewHeight state variables - Changed jumpButton from var to func with containerHeight parameter - Fixed excessive spacing between header and content by using ZStack layout Layout fix: Header image is now in ZStack background with content in foreground, eliminating double spacing that occurred with separate VStacks. Performance: Same PreferenceKey benefits as LegacyView - more efficient scroll tracking.
This commit is contained in:
parent
e9195351aa
commit
37321f31c9
@ -13,8 +13,7 @@ struct BookmarkDetailView2: 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 scrollViewHeight: CGFloat = 1
|
@State private var lastSentProgress: 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
|
||||||
@ -84,62 +83,71 @@ struct BookmarkDetailView2: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var scrollViewContent: some View {
|
private var scrollViewContent: some View {
|
||||||
ScrollView {
|
GeometryReader { geometry in
|
||||||
VStack(spacing: 0) {
|
ScrollView {
|
||||||
// Header image
|
// Invisible GeometryReader to track scroll offset
|
||||||
headerView
|
GeometryReader { scrollGeo in
|
||||||
|
Color.clear.preference(
|
||||||
// Content
|
key: ScrollOffsetPreferenceKey.self,
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
value: CGPoint(
|
||||||
// Spacer for header
|
x: scrollGeo.frame(in: .named("scrollView")).minX,
|
||||||
Color.clear.frame(height: viewModel.bookmarkDetail.imageUrl.isEmpty ? 84 : headerHeight)
|
y: scrollGeo.frame(in: .named("scrollView")).minY
|
||||||
|
)
|
||||||
// Title section
|
)
|
||||||
titleSection
|
}
|
||||||
|
.frame(height: 0)
|
||||||
Divider().padding(.horizontal)
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
// Jump to last position button
|
ZStack(alignment: .top) {
|
||||||
if showJumpToProgressButton {
|
// Header image (in background)
|
||||||
jumpButton
|
headerView
|
||||||
}
|
|
||||||
|
// Content (in foreground)
|
||||||
// Article content (WebView)
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
articleContent
|
// Spacer for header
|
||||||
|
Color.clear.frame(height: viewModel.bookmarkDetail.imageUrl.isEmpty ? 84 : headerHeight)
|
||||||
|
|
||||||
|
// Title section
|
||||||
|
titleSection
|
||||||
|
|
||||||
|
Divider().padding(.horizontal)
|
||||||
|
|
||||||
|
// Jump to last position button
|
||||||
|
if showJumpToProgressButton {
|
||||||
|
jumpButton(containerHeight: geometry.size.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Article content (WebView)
|
||||||
|
articleContent
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.coordinateSpace(name: "scrollView")
|
||||||
|
.clipped()
|
||||||
|
.ignoresSafeArea(edges: .top)
|
||||||
|
.scrollPosition($scrollPosition)
|
||||||
|
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { offset in
|
||||||
|
// Calculate progress from scroll offset
|
||||||
|
let scrollOffset = -offset.y
|
||||||
|
let containerHeight = geometry.size.height
|
||||||
|
let maxOffset = webViewHeight - containerHeight
|
||||||
|
|
||||||
|
guard maxOffset > 0 else { return }
|
||||||
|
|
||||||
|
let rawProgress = scrollOffset / maxOffset
|
||||||
|
let progress = min(max(rawProgress, 0), 1)
|
||||||
|
|
||||||
|
// Only update if change >= 3%
|
||||||
|
let threshold: Double = 0.03
|
||||||
|
if abs(progress - lastSentProgress) >= threshold {
|
||||||
|
lastSentProgress = progress
|
||||||
|
readingProgress = progress
|
||||||
|
viewModel.debouncedUpdateReadProgress(id: bookmarkId, progress: progress, anchor: nil)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.clipped()
|
|
||||||
.ignoresSafeArea(edges: .top)
|
|
||||||
.scrollPosition($scrollPosition)
|
|
||||||
.onScrollGeometryChange(for: CGFloat.self) { geo in
|
|
||||||
geo.contentOffset.y
|
|
||||||
} action: { oldValue, newValue in
|
|
||||||
// Just track current offset, don't calculate yet
|
|
||||||
currentScrollOffset = newValue
|
|
||||||
}
|
|
||||||
.onScrollGeometryChange(for: CGFloat.self) { geo in
|
|
||||||
geo.containerSize.height
|
|
||||||
} action: { oldValue, newValue in
|
|
||||||
scrollViewHeight = newValue
|
|
||||||
}
|
|
||||||
.onScrollPhaseChange { oldPhase, newPhase in
|
|
||||||
// Only calculate progress when scrolling ends
|
|
||||||
if oldPhase == .interacting && newPhase == .idle {
|
|
||||||
let offset = currentScrollOffset
|
|
||||||
let maxOffset = webViewHeight - scrollViewHeight
|
|
||||||
let rawProgress = offset / (maxOffset > 0 ? maxOffset : 1)
|
|
||||||
let progress = min(max(rawProgress, 0), 1)
|
|
||||||
|
|
||||||
// Only update if change is significant (> 5%)
|
|
||||||
let threshold: Double = 0.05
|
|
||||||
if abs(progress - readingProgress) > threshold {
|
|
||||||
readingProgress = progress
|
|
||||||
viewModel.debouncedUpdateReadProgress(id: bookmarkId, progress: progress, anchor: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToolbarContentBuilder
|
@ToolbarContentBuilder
|
||||||
@ -399,9 +407,9 @@ struct BookmarkDetailView2: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var jumpButton: some View {
|
private func jumpButton(containerHeight: CGFloat) -> some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
let maxOffset = webViewHeight - scrollViewHeight
|
let maxOffset = webViewHeight - containerHeight
|
||||||
let offset = maxOffset * (Double(viewModel.readProgress) / 100.0)
|
let offset = maxOffset * (Double(viewModel.readProgress) / 100.0)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||||
scrollPosition = ScrollPosition(y: offset)
|
scrollPosition = ScrollPosition(y: offset)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user