fix(store): replace ZStack overlay sticky approach with native pinnedViews + reliable scroll tracking
- Use LazyVStack(pinnedViews: [.sectionHeaders]) for native section header stickiness - Move ScrollOffsetReader outside LazyVStack (sibling) to prevent lazy-unload breaking scroll tracking - Add baseline capture to ScrollOffsetReader to correctly normalize offset regardless of ScrollView content insets - Apply safeAreaTop padding to categoryTabs only when pinned (isCategoryTabsPinned), preventing oversized header before scroll - Remove complex ZStack dual-tabs overlay, stickyProgress logic, and categoryTabsStickyProgress - categoryTabs now accepts isPinned flag with easeInOut(0.15s) animation on pin transition Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,15 +34,10 @@ struct StoreDetailView: View {
|
||||
@State var didLoad = false
|
||||
@State var categoryHeaderOffsets: [String: CGFloat] = [:]
|
||||
@State var isProgrammaticCategoryScroll = false
|
||||
@State var scrollOffset: CGFloat = 0
|
||||
var safeAreaTop: CGFloat {
|
||||
#if canImport(UIKit)
|
||||
return UIDevice.appSafeAreaTop
|
||||
#else
|
||||
return 0.0
|
||||
#endif
|
||||
}
|
||||
@State var isFavoriteRequestInFlight = false
|
||||
@State var scrollOffset: CGFloat = 0
|
||||
|
||||
var isCategoryTabsPinned: Bool { scrollOffset >= topSectionHeight }
|
||||
|
||||
let cardTopInset: CGFloat = 180
|
||||
let summaryCardBaseHeight: CGFloat = 212
|
||||
@@ -52,48 +47,23 @@ struct StoreDetailView: View {
|
||||
|
||||
var body: some View {
|
||||
ScrollViewReader { proxy in
|
||||
let stickyProgress = categoryTabsStickyProgress(safeTop: safeAreaTop)
|
||||
|
||||
ZStack(alignment: .top) {
|
||||
AppColors.backgroundLight.ignoresSafeArea()
|
||||
|
||||
ScrollView(showsIndicators: false) {
|
||||
LazyVStack(spacing: 0) {
|
||||
topSection
|
||||
Section {
|
||||
sectionedProducts
|
||||
} header: {
|
||||
categoryTabs(proxy: proxy, topExtension: 0, stickyProgress: 1)
|
||||
.opacity(Double(1 - stickyProgress))
|
||||
}
|
||||
}
|
||||
.background(
|
||||
GeometryReader { geometry in
|
||||
Color.clear.preference(
|
||||
key: ScrollOffsetPreferenceKey.self,
|
||||
value: geometry.frame(in: .named(StoreDetailScrollCoordinateSpace.name)).minY
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
.coordinateSpace(name: StoreDetailScrollCoordinateSpace.name)
|
||||
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { minY in
|
||||
let normalizedOffset = max(0, -minY)
|
||||
if abs(scrollOffset - normalizedOffset) > 0.5 {
|
||||
scrollOffset = normalizedOffset
|
||||
ScrollView(showsIndicators: false) {
|
||||
ScrollOffsetReader(offsetY: $scrollOffset)
|
||||
LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
|
||||
topSection
|
||||
Section {
|
||||
sectionedProducts
|
||||
} header: {
|
||||
categoryTabs(proxy: proxy, isPinned: isCategoryTabsPinned)
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
await loadStoreData(forceRefresh: true)
|
||||
}
|
||||
.ignoresSafeArea(edges: .top)
|
||||
|
||||
categoryTabs(proxy: proxy, topExtension: stickyProgress * safeAreaTop, stickyProgress: 1)
|
||||
.opacity(Double(stickyProgress))
|
||||
.allowsHitTesting(stickyProgress > 0.1)
|
||||
.offset(y: safeAreaTop)
|
||||
}
|
||||
.coordinateSpace(name: StoreDetailScrollCoordinateSpace.name)
|
||||
.refreshable {
|
||||
await loadStoreData(forceRefresh: true)
|
||||
}
|
||||
.ignoresSafeArea(edges: .top)
|
||||
.background(AppColors.backgroundLight)
|
||||
.saturation(isStoreOpen ? 1 : 0)
|
||||
}
|
||||
.navigationBarBackButtonHidden(true)
|
||||
|
||||
Reference in New Issue
Block a user