1
1
import SwiftUI
2
2
3
- /**
4
- A view that sticks to the top of the screen in a ScrollView.
5
- When it's bouncing, it stretches the content.
6
- To use this view, you need to call ``View.enableStickyHeader()`` modifier to the ScrollView.
7
- */
3
+ public struct StickyHeaderContext {
4
+ public let topMargin : CGFloat
5
+
6
+ init ( topMargin: CGFloat ) {
7
+ self . topMargin = topMargin
8
+ }
9
+ }
10
+
11
+ ///
12
+ /// A view that sticks to the top of the screen in a ScrollView.
13
+ /// When it's bouncing, it stretches the content.
14
+ /// To use this view, you need to call ``View.enableStickyHeader()`` modifier to the ScrollView.
8
15
public struct StickyHeader < Content: View > : View {
9
16
10
17
/**
@@ -18,49 +25,65 @@ public struct StickyHeader<Content: View>: View {
18
25
}
19
26
20
27
public let sizing : Sizing
21
- public let content : Content
28
+ public let content : ( StickyHeaderContext ) -> Content
22
29
23
30
@State var baseContentHeight : CGFloat ?
24
31
@State var stretchingValue : CGFloat = 0
32
+ @State var topMargin : CGFloat = 0
25
33
26
34
public init (
27
35
sizing: Sizing ,
28
- @ViewBuilder content: ( ) -> Content
36
+ @ViewBuilder content: @escaping ( StickyHeaderContext ) -> Content
29
37
) {
30
38
self . sizing = sizing
31
- self . content = content ( )
39
+ self . content = content
32
40
}
33
41
34
42
public var body : some View {
35
-
43
+
36
44
let offsetY : CGFloat = 0
37
-
45
+
46
+ let context = StickyHeaderContext (
47
+ topMargin: topMargin
48
+ )
49
+
38
50
Group {
39
51
switch sizing {
40
52
case . content:
41
- content
53
+ content ( context )
42
54
. onGeometryChange ( for: CGSize . self, of: \. size) { size in
43
55
if stretchingValue == 0 {
44
56
baseContentHeight = size. height
45
57
}
46
58
}
47
- . frame ( height: baseContentHeight. map {
48
- $0 + stretchingValue
49
- } )
59
+ . frame (
60
+ height: baseContentHeight. map {
61
+ $0 + stretchingValue
62
+ }
63
+ )
50
64
. offset ( y: - stretchingValue)
51
- // container
65
+ // container
52
66
. frame ( height: baseContentHeight, alignment: . top)
53
67
54
68
case . fixed( let height) :
55
-
56
- content
69
+
70
+ content ( context )
57
71
. frame ( height: height + stretchingValue + offsetY)
58
72
. offset ( y: - offsetY)
59
73
. offset ( y: - stretchingValue)
60
- // container
74
+ // container
61
75
. frame ( height: height, alignment: . top)
62
76
}
63
- }
77
+ }
78
+ . onGeometryChange (
79
+ for: CGRect . self,
80
+ of: {
81
+ $0. frame ( in: . global)
82
+ } ,
83
+ action: { value in
84
+ topMargin = value. minY
85
+ }
86
+ )
64
87
. onGeometryChange (
65
88
for: CGRect . self,
66
89
of: {
@@ -85,26 +108,26 @@ extension View {
85
108
86
109
#Preview( " dynamic " ) {
87
110
ScrollView {
88
-
89
- StickyHeader ( sizing: . content) {
90
-
111
+
112
+ StickyHeader ( sizing: . content) { context in
113
+
91
114
ZStack {
92
-
93
- Color . red
94
- . padding ( . top, - 100 )
95
-
115
+
116
+ Color . red
117
+ . padding ( . top, - context . topMargin )
118
+
96
119
VStack {
97
120
Text ( " StickyHeader " )
98
121
Text ( " StickyHeader " )
99
122
Text ( " StickyHeader " )
100
123
}
101
124
. border ( Color . red)
102
125
. frame ( maxWidth: . infinity, maxHeight: . infinity)
103
- // .background(.yellow)
126
+ // .background(.yellow)
104
127
}
105
-
128
+
106
129
}
107
-
130
+
108
131
ForEach ( 0 ..< 100 , id: \. self) { _ in
109
132
Text ( " Hello World! " )
110
133
. frame ( maxWidth: . infinity)
@@ -116,13 +139,13 @@ extension View {
116
139
117
140
#Preview( " dynamic full " ) {
118
141
ScrollView {
119
-
120
- StickyHeader ( sizing: . content) {
121
-
142
+
143
+ StickyHeader ( sizing: . content) { context in
144
+
122
145
ZStack {
123
-
146
+
124
147
Color . red
125
-
148
+
126
149
VStack {
127
150
Text ( " StickyHeader " )
128
151
Text ( " StickyHeader " )
@@ -134,12 +157,12 @@ extension View {
134
157
. background (
135
158
Color . green
136
159
. padding ( . top, - 100 )
137
-
160
+
138
161
)
139
162
}
140
-
163
+
141
164
}
142
-
165
+
143
166
ForEach ( 0 ..< 100 , id: \. self) { _ in
144
167
Text ( " Hello World! " )
145
168
. frame ( maxWidth: . infinity)
@@ -150,9 +173,9 @@ extension View {
150
173
151
174
#Preview( " fixed " ) {
152
175
ScrollView {
153
-
154
- StickyHeader ( sizing: . fixed( 300 ) ) {
155
-
176
+
177
+ StickyHeader ( sizing: . fixed( 300 ) ) { context in
178
+
156
179
Rectangle ( )
157
180
. stroke ( lineWidth: 10 )
158
181
. overlay (
@@ -163,7 +186,7 @@ extension View {
163
186
}
164
187
)
165
188
}
166
-
189
+
167
190
ForEach ( 0 ..< 100 , id: \. self) { _ in
168
191
Text ( " Hello World! " )
169
192
. frame ( maxWidth: . infinity)
@@ -175,29 +198,29 @@ extension View {
175
198
176
199
#Preview( " fixed full " ) {
177
200
ScrollView {
178
-
179
- StickyHeader ( sizing: . fixed( 300 ) ) {
180
-
201
+
202
+ StickyHeader ( sizing: . fixed( 300 ) ) { context in
203
+
181
204
ZStack {
182
-
205
+
183
206
Color . red
184
-
207
+
185
208
VStack {
186
209
Text ( " StickyHeader " )
187
210
Text ( " StickyHeader " )
188
211
Text ( " StickyHeader " )
189
212
}
190
213
. border ( Color . red)
191
214
. frame ( maxWidth: . infinity, maxHeight: . infinity)
192
- // .background(.yellow)
215
+ // .background(.yellow)
193
216
. background (
194
217
Color . green
195
- . padding ( . top, - 100 )
218
+ . padding ( . top, - context . topMargin )
196
219
197
220
)
198
221
}
199
222
}
200
-
223
+
201
224
ForEach ( 0 ..< 100 , id: \. self) { _ in
202
225
Text ( " Hello World! " )
203
226
. frame ( maxWidth: . infinity)
0 commit comments