5
5
6
6
import SwiftUI
7
7
8
- /// The transition type for the whole NavigationStackView.
9
- public enum NavigationTransition {
10
- /// Transitions won't be animated.
11
- case none
12
-
13
- /// Use the [default transition](x-source-tag://defaultTransition).
14
- case `default`
15
-
16
- /// Use a custom transition (the transition will be applied both to push and pop operations).
17
- case custom( AnyTransition )
18
-
19
- /// A right-to-left slide transition on push, a left-to-right slide transition on pop.
20
- /// - Tag: defaultTransition
21
- public static var defaultTransitions : ( push: AnyTransition , pop: AnyTransition ) {
22
- let pushTrans = AnyTransition . asymmetric ( insertion: . move( edge: . trailing) , removal: . move( edge: . leading) )
23
- let popTrans = AnyTransition . asymmetric ( insertion: . move( edge: . leading) , removal: . move( edge: . trailing) )
24
- return ( pushTrans, popTrans)
25
- }
26
- }
27
-
28
- private enum NavigationType {
8
+ enum NavigationType {
29
9
case push
30
10
case pop
31
11
}
32
12
33
- /// Defines the type of a pop operation.
34
- public enum PopDestination {
35
- /// Pop back to the previous view.
36
- case previous
37
-
38
- /// Pop back to the root view (i.e. the first view added to the NavigationStackView during the initialization process).
39
- case root
40
-
41
- /// Pop back to a view identified by a specific ID.
42
- case view( withId: String )
43
- }
44
-
45
- // MARK: ViewModel
46
-
47
13
public class NavigationStack : ObservableObject {
48
14
49
15
/// Default transition animation
50
16
public static let defaultEasing = Animation . easeOut ( duration: 0.2 )
51
17
52
- fileprivate private( set) var navigationType = NavigationType . push
18
+ private( set) var navigationType = NavigationType . push
53
19
54
20
/// Customizable easing to apply in pop and push transitions
55
21
private let easing : Animation
@@ -64,7 +30,7 @@ public class NavigationStack: ObservableObject {
64
30
}
65
31
}
66
32
67
- @Published fileprivate var currentView : ViewElement ?
33
+ @Published var currentView : ViewElement ?
68
34
69
35
/// Navigates to a view.
70
36
/// - Parameters:
@@ -135,221 +101,11 @@ public class NavigationStack: ObservableObject {
135
101
}
136
102
137
103
//the actual element in the stack
138
- private struct ViewElement : Identifiable , Equatable {
104
+ struct ViewElement : Identifiable , Equatable {
139
105
let id : String
140
106
let wrappedElement : AnyView
141
107
142
108
static func == ( lhs: ViewElement , rhs: ViewElement ) -> Bool {
143
109
lhs. id == rhs. id
144
110
}
145
111
}
146
-
147
- // MARK: Views
148
-
149
- /// An alternative SwiftUI NavigationView implementing classic stack-based navigation giving also some more control on animations and programmatic navigation.
150
- public struct NavigationStackView < Root> : View where Root: View {
151
- @ObservedObject private var navViewModel : NavigationStack
152
- private let rootView : Root
153
- private let transitions : ( push: AnyTransition , pop: AnyTransition )
154
-
155
- /// Creates a NavigationStackView.
156
- /// - Parameters:
157
- /// - transitionType: The type of transition to apply between views in every push and pop operation.
158
- /// - easing: The easing function to apply to every push and pop operation.
159
- /// - rootView: The very first view in the NavigationStack.
160
- public init ( transitionType: NavigationTransition = . default, easing: Animation = NavigationStack . defaultEasing, @ViewBuilder rootView: ( ) -> Root ) {
161
- self . init ( transitionType: transitionType, navigationStack: NavigationStack ( easing: easing) , rootView: rootView)
162
- }
163
-
164
- /// Creates a NavigationStackView with the provided NavigationStack
165
- /// - Parameters:
166
- /// - transitionType: The type of transition to apply between views in every push and pop operation.
167
- /// - navigationStack: the shared NavigationStack
168
- /// - rootView: The very first view in the NavigationStack.
169
- public init ( transitionType: NavigationTransition = . default, navigationStack: NavigationStack , @ViewBuilder rootView: ( ) -> Root ) {
170
- self . rootView = rootView ( )
171
- self . navViewModel = navigationStack
172
- switch transitionType {
173
- case . none:
174
- self . transitions = ( . identity, . identity)
175
- case . custom( let trans) :
176
- self . transitions = ( trans, trans)
177
- default :
178
- self . transitions = NavigationTransition . defaultTransitions
179
- }
180
- }
181
-
182
- public var body : some View {
183
- let showRoot = navViewModel. currentView == nil
184
- let navigationType = navViewModel. navigationType
185
-
186
- return ZStack {
187
- Group {
188
- if showRoot {
189
- rootView
190
- . transition ( navigationType == . push ? transitions. push : transitions. pop)
191
- . environmentObject ( navViewModel)
192
- } else {
193
- navViewModel. currentView!. wrappedElement
194
- . transition ( navigationType == . push ? transitions. push : transitions. pop)
195
- . environmentObject ( navViewModel)
196
- }
197
- }
198
- }
199
- }
200
- }
201
-
202
- /// A view used to navigate to another view through its enclosing NavigationStack.
203
- public struct PushView < Label, Destination, Tag> : View where Label: View , Destination: View , Tag: Hashable {
204
- @EnvironmentObject private var navViewModel : NavigationStack
205
- private let label : Label ?
206
- private let destinationId : String ?
207
- private let destination : Destination
208
- private let tag : Tag ?
209
- @Binding private var isActive : Bool
210
- @Binding private var selection : Tag ?
211
-
212
- /// Creates a PushView that triggers the navigation on tap or when a tag matches a specific value.
213
- /// - Parameters:
214
- /// - destination: The view to navigate to.
215
- /// - destinationId: The ID of the destination view (used to easily come back to it if needed).
216
- /// - tag: A value representing this push operation.
217
- /// - selection: A binding that triggers the navigation if and when its value matches the tag value.
218
- /// - label: The actual view to tap to trigger the navigation.
219
- public init ( destination: Destination , destinationId: String ? = nil , tag: Tag , selection: Binding < Tag ? > ,
220
- @ViewBuilder label: ( ) -> Label ) {
221
- self . init ( destination: destination, destinationId: destinationId, isActive: Binding . constant ( false ) ,
222
- tag: tag, selection: selection, label: label)
223
- }
224
-
225
- private init ( destination: Destination , destinationId: String ? , isActive: Binding < Bool > ,
226
- tag: Tag ? , selection: Binding < Tag ? > , @ViewBuilder label: ( ) -> Label ) {
227
- self . label = label ( )
228
- self . destinationId = destinationId
229
- self . _isActive = isActive
230
- self . tag = tag
231
- self . destination = destination
232
- self . _selection = selection
233
- }
234
-
235
- public var body : some View {
236
- if let selection = selection, let tag = tag, selection == tag {
237
- DispatchQueue . main. async {
238
- self . selection = nil
239
- self . push ( )
240
- }
241
- }
242
- if isActive {
243
- DispatchQueue . main. async {
244
- self . isActive = false
245
- self . push ( )
246
- }
247
- }
248
- return label. onTapGesture {
249
- self . push ( )
250
- }
251
- }
252
-
253
- private func push( ) {
254
- self . navViewModel. push ( self . destination, withId: self . destinationId)
255
- }
256
- }
257
-
258
- public extension PushView where Tag == Never {
259
-
260
- /// Creates a PushView that triggers the navigation on tap.
261
- /// - Parameters:
262
- /// - destination: The view to navigate to.
263
- /// - destinationId: The ID of the destination view (used to easily come back to it if needed).
264
- /// - label: The actual view to tap to trigger the navigation.
265
- init ( destination: Destination , destinationId: String ? = nil , @ViewBuilder label: ( ) -> Label ) {
266
- self . init ( destination: destination, destinationId: destinationId, isActive: Binding . constant ( false ) ,
267
- tag: nil , selection: Binding . constant ( nil ) , label: label)
268
- }
269
-
270
- /// Creates a PushView that triggers the navigation on tap or when a boolean value becomes true.
271
- /// - Parameters:
272
- /// - destination: The view to navigate to.
273
- /// - destinationId: The ID of the destination view (used to easily come back to it if needed).
274
- /// - isActive: A boolean binding that triggers the navigation if and when becomes true.
275
- /// - label: The actual view to tap to trigger the navigation.
276
- init ( destination: Destination , destinationId: String ? = nil ,
277
- isActive: Binding < Bool > , @ViewBuilder label: ( ) -> Label ) {
278
- self . init ( destination: destination, destinationId: destinationId, isActive: isActive,
279
- tag: nil , selection: Binding . constant ( nil ) , label: label)
280
- }
281
- }
282
-
283
- /// A view used to navigate back to a previous view through its enclosing NavigationStack.
284
- public struct PopView < Label, Tag> : View where Label: View , Tag: Hashable {
285
- @EnvironmentObject private var navViewModel : NavigationStack
286
- private let label : Label
287
- private let destination : PopDestination
288
- private let tag : Tag ?
289
- @Binding private var isActive : Bool
290
- @Binding private var selection : Tag ?
291
-
292
- /// Creates a PopView that triggers the navigation on tap or when a tag matches a specific value.
293
- /// - Parameters:
294
- /// - destination: The destination type of the pop operation.
295
- /// - tag: A value representing this pop operation.
296
- /// - selection: A binding that triggers the navigation if and when its value matches the tag value.
297
- /// - label: The actual view to tap to trigger the navigation.
298
- public init ( destination: PopDestination = . previous, tag: Tag , selection: Binding < Tag ? > , @ViewBuilder label: ( ) -> Label ) {
299
- self . init ( destination: destination, isActive: Binding . constant ( false ) ,
300
- tag: tag, selection: selection, label: label)
301
- }
302
-
303
- private init ( destination: PopDestination , isActive: Binding < Bool > , tag: Tag ? ,
304
- selection: Binding < Tag ? > , @ViewBuilder label: ( ) -> Label ) {
305
- self . label = label ( )
306
- self . destination = destination
307
- self . _isActive = isActive
308
- self . _selection = selection
309
- self . tag = tag
310
- }
311
-
312
- public var body : some View {
313
- if let selection = selection, let tag = tag, selection == tag {
314
- DispatchQueue . main. async {
315
- self . selection = nil
316
- self . pop ( )
317
- }
318
- }
319
- if isActive {
320
- DispatchQueue . main. async {
321
- self . isActive = false
322
- self . pop ( )
323
- }
324
- }
325
- return label. onTapGesture {
326
- self . pop ( )
327
- }
328
- }
329
-
330
- private func pop( ) {
331
- self . navViewModel. pop ( to: self . destination)
332
- }
333
- }
334
-
335
- public extension PopView where Tag == Never {
336
-
337
- /// Creates a PopView that triggers the navigation on tap.
338
- /// - Parameters:
339
- /// - destination: The destination type of the pop operation.
340
- /// - label: The actual view to tap to trigger the navigation.
341
- init ( destination: PopDestination = . previous, @ViewBuilder label: ( ) -> Label ) {
342
- self . init ( destination: destination, isActive: Binding . constant ( false ) ,
343
- tag: nil , selection: Binding . constant ( nil ) , label: label)
344
- }
345
-
346
- /// Creates a PopView that triggers the navigation on tap or when a boolean value becomes true.
347
- /// - Parameters:
348
- /// - destination: The destination type of the pop operation.
349
- /// - isActive: A boolean binding that triggers the navigation if and when becomes true.
350
- /// - label: The actual view to tap to trigger the navigation.
351
- init ( destination: PopDestination = . previous, isActive: Binding < Bool > , @ViewBuilder label: ( ) -> Label ) {
352
- self . init ( destination: destination, isActive: isActive,
353
- tag: nil , selection: Binding . constant ( nil ) , label: label)
354
- }
355
- }
0 commit comments