@@ -18,7 +18,9 @@ public struct CategoryDetailView: View {
1818 /// - Properties
1919 @Perception . Bindable
2020 public var store : StoreOf < CategoryDetailFeature >
21-
21+ @State private var currentOffset : CGFloat = 0
22+ @State private var targetOffset : CGFloat = 0
23+ @State private var isSticky : Bool = false
2224 /// - Initializer
2325 public init ( store: StoreOf < CategoryDetailFeature > ) {
2426 self . store = store
@@ -28,12 +30,36 @@ public struct CategoryDetailView: View {
2830public extension CategoryDetailView {
2931 var body : some View {
3032 WithPerceptionTracking {
31- VStack ( spacing: 24 ) {
32- header
33- PokitDivider ( ) . padding ( . horizontal, - 20 )
34- filterHeader
35- contentScrollView
33+ ScrollView {
34+ VStack ( spacing: 24 ) {
35+ header
36+ scrollObservableView
37+ LazyVStack ( spacing: 0 , pinnedViews: [ . sectionHeaders] ) {
38+ Section {
39+ contentScrollView
40+ } header: {
41+ VStack ( spacing: 24 ) {
42+ PokitDivider ( ) . padding ( . horizontal, - 20 )
43+ filterHeader
44+ }
45+ . padding ( . bottom, 16 )
46+ . background ( . white)
47+ }
48+ }
49+ }
3650 }
51+ . onPreferenceChange ( ScrollOffsetKey . self) {
52+ if $0 != targetOffset {
53+ currentOffset = $0
54+ }
55+ }
56+ . onChange ( of: currentOffset, perform: { newOffSet in
57+ if newOffSet != targetOffset && newOffSet + targetOffset < 10 {
58+ isSticky = true
59+ } else {
60+ isSticky = false
61+ }
62+ } )
3763 . padding ( . horizontal, 20 )
3864 . padding ( . top, 12 )
3965 . pokitNavigationBar { navigationBar }
@@ -89,7 +115,7 @@ public extension CategoryDetailView {
89115//MARK: - Configure View
90116private extension CategoryDetailView {
91117 var navigationBar : some View {
92- PokitHeader {
118+ PokitHeader ( title : isSticky ? store . category . categoryName : " " ) {
93119 PokitHeaderItems ( placement: . leading) {
94120 PokitToolbarButton (
95121 . icon( . arrowLeft) ,
@@ -242,31 +268,29 @@ private extension CategoryDetailView {
242268 Spacer ( )
243269 }
244270 } else {
245- ScrollView ( showsIndicators: false ) {
246- LazyVStack ( spacing: 0 ) {
247- ForEach (
248- Array ( store. scope ( state: \. contents, action: \. contents) )
249- ) { store in
250- let isFirst = store. state. id == self . store. contents. first? . id
251- let isLast = store. state. id == self . store. contents. last? . id
252-
253- ContentCardView (
254- store: store,
255- type: . linkList,
256- isFirst: isFirst,
257- isLast: isLast
258- )
259- }
271+ LazyVStack ( spacing: 0 ) {
272+ ForEach (
273+ Array ( store. scope ( state: \. contents, action: \. contents) )
274+ ) { store in
275+ let isFirst = store. state. id == self . store. contents. first? . id
276+ let isLast = store. state. id == self . store. contents. last? . id
260277
261- if store. hasNext {
262- PokitLoading ( )
263- . task { await send ( . pagenation) . finish ( ) }
264- }
265-
266- Spacer ( )
278+ ContentCardView (
279+ store: store,
280+ type: . linkList,
281+ isFirst: isFirst,
282+ isLast: isLast
283+ )
284+ }
285+
286+ if store. hasNext {
287+ PokitLoading ( )
288+ . task { await send ( . pagenation) . finish ( ) }
267289 }
268- . padding ( . bottom, 36 )
290+
291+ Spacer ( )
269292 }
293+ . padding ( . bottom, 36 )
270294 }
271295 } else {
272296 PokitLoading ( )
@@ -298,6 +322,18 @@ private extension CategoryDetailView {
298322 )
299323 }
300324 }
325+ private var scrollObservableView : some View {
326+ GeometryReader { proxy in
327+ let offsetY = proxy. frame ( in: . global) . origin. y
328+ Color . clear
329+ . preference (
330+ key: ScrollOffsetKey . self,
331+ value: offsetY
332+ )
333+ . onAppear { targetOffset = offsetY }
334+ }
335+ . frame ( height: 0 )
336+ }
301337}
302338//MARK: - Preview
303339#Preview {
@@ -324,5 +360,3 @@ private extension CategoryDetailView {
324360 )
325361 }
326362}
327-
328-
0 commit comments