Skip to content

Commit 7977bb9

Browse files
committed
[fix] #193 상단헤더 스크롤 시 고정 로직 추가
1 parent 1647d74 commit 7977bb9

File tree

2 files changed

+80
-31
lines changed

2 files changed

+80
-31
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// ScrollOffsetKey.swift
3+
// DSKit
4+
//
5+
// Created by 김민호 on 5/18/25.
6+
//
7+
8+
import SwiftUI
9+
10+
public struct ScrollOffsetKey: PreferenceKey {
11+
public static var defaultValue: CGFloat = 0
12+
public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
13+
value += nextValue()
14+
}
15+
}

Projects/Feature/FeatureCategoryDetail/Sources/CategoryDetailView.swift

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
2830
public 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
90116
private 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

Comments
 (0)