@@ -33,7 +33,6 @@ import SwiftUI
33
33
/// - tabs: An array of values conforming to `Hashable` that represent the tabs. The order of tabs in this array **must** be reflected in the TabBar view provided.
34
34
/// - selection: The currently selected tab.
35
35
/// - content: A closure that provides the content for each tab. The order and number of elements in the closure **must** match the `tabs` parameter.
36
- @available ( iOS 13 . 0 , macOS 10 . 15 , * )
37
36
public struct CustomTabView < SelectionValue: Hashable , TabBarView: View , Content: View > : View {
38
37
39
38
// Tabs
@@ -61,15 +60,15 @@ public struct CustomTabView<SelectionValue: Hashable, TabBarView: View, Content:
61
60
public var body : some View {
62
61
if #available( iOS 18 . 0 , macOS 15 . 0 , * ) {
63
62
Group ( subviews: content) { subviews in
64
- _LayoutView < TabBarView , SelectionValue > (
63
+ _TabBarLayoutView (
65
64
tabBarView: tabBarView,
66
65
selectedTabIndex: tabIndices [ selection] ?? 0 ,
67
- children : subviews
66
+ subviews : subviews
68
67
)
69
68
}
70
69
} else {
71
70
_VariadicView. Tree (
72
- _VariadicViewLayout < TabBarView , SelectionValue > (
71
+ _VariadicViewLayout < TabBarView > (
73
72
tabBarView: tabBarView,
74
73
selectedTabIndex: tabIndices [ selection] ?? 0
75
74
)
@@ -80,253 +79,88 @@ public struct CustomTabView<SelectionValue: Hashable, TabBarView: View, Content:
80
79
}
81
80
}
82
81
83
- private struct _VariadicViewLayout < TabBarView: View , SelectionValue: Hashable > : _VariadicView_UnaryViewRoot {
84
- @Environment ( \. layoutDirection) private var layoutDirection : LayoutDirection
85
- @Environment ( \. tabBarPosition) private var tabBarPosition : TabBarPosition
86
-
82
+ private struct _VariadicViewLayout < TabBarView: View > : _VariadicView_UnaryViewRoot {
87
83
let tabBarView : TabBarView
88
84
let selectedTabIndex : Int
89
85
90
- private func contentView( children: _VariadicView . Children ) -> some View {
91
- #if canImport(UIKit)
92
- return UITabBarControllerRepresentable (
93
- selectedTabIndex: selectedTabIndex,
94
- controlledViews: children. map { UIHostingController ( rootView: $0) }
95
- )
96
- #elseif canImport(AppKit)
97
- return NSTabViewControllerRepresentable (
98
- selectedTabIndex: selectedTabIndex,
99
- controlledViews: children. map { NSHostingController ( rootView: $0) }
100
- )
101
- #endif
102
- }
103
-
104
- private func topBarView( children: _VariadicView . Children ) -> some View {
105
- VStack ( spacing: 0 ) {
106
- tabBarView
107
-
108
- contentView ( children: children)
109
- }
110
- }
111
-
112
- private func bottomBarView( children: _VariadicView . Children ) -> some View {
113
- VStack ( spacing: 0 ) {
114
- contentView ( children: children)
115
-
116
- tabBarView
117
- }
118
- }
119
-
120
- #if canImport(UIKit)
121
- // Keyboard visibility
122
- @State private var isKeyboardVisible : Bool = false
123
-
124
- private func bottomBarViewiOS13( children: _VariadicView . Children ) -> some View {
125
- VStack ( spacing: 0 ) {
126
- contentView ( children: children)
127
-
128
- if !isKeyboardVisible {
129
- tabBarView
130
- }
131
- }
132
- . onReceive ( keyboardPublisher) { isKeyboardVisible in
133
- self . isKeyboardVisible = isKeyboardVisible
134
- }
135
- }
136
- #endif
137
-
138
- private func leftBarView( children: _VariadicView . Children ) -> some View {
139
- HStack ( spacing: 0 ) {
140
- tabBarView
141
-
142
- contentView ( children: children)
143
- }
144
- }
145
-
146
- private func rightBarView( children: _VariadicView . Children ) -> some View {
147
- HStack ( spacing: 0 ) {
148
- contentView ( children: children)
149
-
150
- tabBarView
151
- }
152
- }
153
-
154
- private func floatingBarView( children: _VariadicView . Children , edge: Edge ) -> some View {
155
- let alignment : Alignment
156
- switch edge {
157
- case . top:
158
- alignment = . top
159
- case . leading:
160
- alignment = . leading
161
- case . bottom:
162
- alignment = . bottom
163
- case . trailing:
164
- alignment = . trailing
165
- }
166
-
167
- return ZStack ( alignment: alignment, content: {
168
- contentView ( children: children)
169
-
170
- tabBarView
171
- } )
172
- }
173
-
174
86
@ViewBuilder
175
87
func body( children: _VariadicView . Children ) -> some View {
176
- switch tabBarPosition {
177
- case . edge( let edge) :
178
- switch edge {
179
- case . top:
180
- topBarView ( children: children)
181
- case . leading:
182
- switch layoutDirection {
183
- case . leftToRight:
184
- leftBarView ( children: children)
185
- case . rightToLeft:
186
- rightBarView ( children: children)
187
- @unknown default :
188
- leftBarView ( children: children)
189
- }
190
- case . bottom:
191
- #if canImport(UIKit)
192
- if #available( iOS 14 , * ) {
193
- bottomBarView ( children: children)
194
- . ignoresSafeArea ( . keyboard, edges: . bottom)
195
- } else {
196
- bottomBarViewiOS13 ( children: children)
197
- }
198
- #elseif canImport(AppKit)
199
- bottomBarView ( children: children)
200
- #endif
201
- case . trailing:
202
- switch layoutDirection {
203
- case . leftToRight:
204
- rightBarView ( children: children)
205
- case . rightToLeft:
206
- leftBarView ( children: children)
207
- @unknown default :
208
- rightBarView ( children: children)
209
- }
210
- }
211
- case . floating( let edge) :
212
- floatingBarView ( children: children, edge: edge)
213
- }
88
+ _TabBarLayoutView ( tabBarView: tabBarView, selectedTabIndex: selectedTabIndex, subviews: children)
214
89
}
215
90
}
216
91
217
- #if canImport(UIKit)
218
- extension _VariadicViewLayout : KeyboardReadable { }
219
- #endif
220
-
221
- @available ( iOS 18 . 0 , macOS 15 . 0 , * )
222
- private struct _LayoutView < TabBarView: View , SelectionValue: Hashable > : View {
92
+ struct _TabBarLayoutView < TabBarView: View , Subviews> : View where Subviews: RandomAccessCollection , Subviews. Element: View & Identifiable , Subviews. Index == Int {
223
93
@Environment ( \. layoutDirection) private var layoutDirection : LayoutDirection
224
- @Environment ( \. tabBarPosition) private var tabBarPosition : TabBarPosition
225
-
94
+ @Environment ( \. tabBarEdge) private var tabBarEdge : Edge
95
+
96
+ @State private var tabBarVisibility : [ Int : TabBarVisibility ] = [ : ]
97
+ @State private var tabBarHeight : CGFloat = 0
98
+
226
99
let tabBarView : TabBarView
227
100
let selectedTabIndex : Int
228
- let children : SubviewsCollection
101
+ let subviews : Subviews
102
+
103
+ var body : some View {
104
+ GeometryReader { proxy in
105
+ ZStack ( alignment: tabBarAlignment, content: {
106
+ contentView (
107
+ subviews: subviews,
108
+ additionalSafeAreaInsets: . init( ) ,
109
+ additionalSafeAreaInsetsTabBarVisible: additionalSafeAreaInsetsTabBarVisible
110
+ )
111
+ #if canImport(UIKit)
112
+ . ignoresSafeArea( . all, edges: . vertical)
113
+ #endif
114
+ tabBarView
115
+ . opacity ( tabBarVisibility [ selectedTabIndex] == . hidden ? 0 : 1 )
116
+ . onPreferenceChange ( TabBarBoundsForSafeAreaKey . self) {
117
+ if let anchor = $0 {
118
+ tabBarHeight = proxy [ anchor] . height }
119
+ }
120
+ } )
121
+ }
122
+ }
229
123
230
- private func contentView( children : SubviewsCollection ) -> some View {
124
+ private func contentView( subviews : Subviews , additionalSafeAreaInsets : EdgeInsets , additionalSafeAreaInsetsTabBarVisible : EdgeInsets ) -> some View {
231
125
#if canImport(UIKit)
232
126
return UITabBarControllerRepresentable (
233
127
selectedTabIndex: selectedTabIndex,
234
- controlledViews: children. map { UIHostingController ( rootView: $0) }
128
+ tabBarVisibility: $tabBarVisibility,
129
+ controlledViews: subviews,
130
+ additionalSafeAreaInsets: additionalSafeAreaInsets,
131
+ additionalSafeAreaInsetsTabBarVisible: additionalSafeAreaInsetsTabBarVisible
235
132
)
236
133
#elseif canImport(AppKit)
237
134
return NSTabViewControllerRepresentable (
238
135
selectedTabIndex: selectedTabIndex,
239
- controlledViews: children . map { NSHostingController ( rootView: $0) }
136
+ controlledViews: subviews . map { NSHostingController ( rootView: $0) }
240
137
)
241
138
#endif
242
139
}
243
-
244
- private func topBarView( children: SubviewsCollection ) -> some View {
245
- VStack ( spacing: 0 ) {
246
- tabBarView
247
-
248
- contentView ( children: children)
249
- }
250
- }
251
-
252
- private func bottomBarView( children: SubviewsCollection ) -> some View {
253
- VStack ( spacing: 0 ) {
254
- contentView ( children: children)
255
-
256
- tabBarView
257
- }
258
- }
259
-
260
- private func leftBarView( children: SubviewsCollection ) -> some View {
261
- HStack ( spacing: 0 ) {
262
- tabBarView
263
-
264
- contentView ( children: children)
265
- }
266
- }
267
-
268
- private func rightBarView( children: SubviewsCollection ) -> some View {
269
- HStack ( spacing: 0 ) {
270
- contentView ( children: children)
271
-
272
- tabBarView
273
- }
274
- }
275
-
276
- private func floatingBarView( children: SubviewsCollection , edge: Edge ) -> some View {
277
- let alignment : Alignment
278
- switch edge {
140
+
141
+ private var additionalSafeAreaInsetsTabBarVisible : EdgeInsets {
142
+ switch tabBarEdge {
279
143
case . top:
280
- alignment = . top
144
+ . init ( top: tabBarHeight , leading : 0 , bottom : 0 , trailing : 0 )
281
145
case . leading:
282
- alignment = . leading
146
+ . init ( top : 0 , leading: tabBarHeight , bottom : 0 , trailing : 0 )
283
147
case . bottom:
284
- alignment = . bottom
148
+ . init ( top : 0 , leading : 0 , bottom: tabBarHeight , trailing : 0 )
285
149
case . trailing:
286
- alignment = . trailing
150
+ . init ( top : 0 , leading : 0 , bottom : 0 , trailing: tabBarHeight )
287
151
}
288
-
289
- return ZStack ( alignment: alignment, content: {
290
- contentView ( children: children)
291
-
292
- tabBarView
293
- } )
294
152
}
295
-
296
- var body : some View {
297
- switch tabBarPosition {
298
- case . edge( let edge) :
299
- switch edge {
300
- case . top:
301
- topBarView ( children: children)
302
- case . leading:
303
- switch layoutDirection {
304
- case . leftToRight:
305
- leftBarView ( children: children)
306
- case . rightToLeft:
307
- rightBarView ( children: children)
308
- @unknown default :
309
- leftBarView ( children: children)
310
- }
311
- case . bottom:
312
- #if canImport(UIKit)
313
- bottomBarView ( children: children)
314
- . ignoresSafeArea ( . keyboard, edges: . bottom)
315
- #elseif canImport(AppKit)
316
- bottomBarView ( children: children)
317
- #endif
318
- case . trailing:
319
- switch layoutDirection {
320
- case . leftToRight:
321
- rightBarView ( children: children)
322
- case . rightToLeft:
323
- leftBarView ( children: children)
324
- @unknown default :
325
- rightBarView ( children: children)
326
- }
327
- }
328
- case . floating( let edge) :
329
- floatingBarView ( children: children, edge: edge)
153
+
154
+ private var tabBarAlignment : Alignment {
155
+ switch tabBarEdge {
156
+ case . top:
157
+ . top
158
+ case . leading:
159
+ . leading
160
+ case . bottom:
161
+ . bottom
162
+ case . trailing:
163
+ . trailing
330
164
}
331
165
}
332
166
}
0 commit comments