Skip to content

Commit c8f0ea3

Browse files
committed
Separated entitites into different files.
1 parent a8d99fc commit c8f0ea3

File tree

4 files changed

+268
-248
lines changed

4 files changed

+268
-248
lines changed

Sources/NavigationStack/NavigationStack.swift

Lines changed: 4 additions & 248 deletions
Original file line numberDiff line numberDiff line change
@@ -5,51 +5,17 @@
55

66
import SwiftUI
77

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 {
299
case push
3010
case pop
3111
}
3212

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-
4713
public class NavigationStack: ObservableObject {
4814

4915
/// Default transition animation
5016
public static let defaultEasing = Animation.easeOut(duration: 0.2)
5117

52-
fileprivate private(set) var navigationType = NavigationType.push
18+
private(set) var navigationType = NavigationType.push
5319

5420
/// Customizable easing to apply in pop and push transitions
5521
private let easing: Animation
@@ -64,7 +30,7 @@ public class NavigationStack: ObservableObject {
6430
}
6531
}
6632

67-
@Published fileprivate var currentView: ViewElement?
33+
@Published var currentView: ViewElement?
6834

6935
/// Navigates to a view.
7036
/// - Parameters:
@@ -135,221 +101,11 @@ public class NavigationStack: ObservableObject {
135101
}
136102

137103
//the actual element in the stack
138-
private struct ViewElement: Identifiable, Equatable {
104+
struct ViewElement: Identifiable, Equatable {
139105
let id: String
140106
let wrappedElement: AnyView
141107

142108
static func == (lhs: ViewElement, rhs: ViewElement) -> Bool {
143109
lhs.id == rhs.id
144110
}
145111
}
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-
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// NavigationStackView.swift
3+
//
4+
//
5+
// Created by Matteo Puccinelli on 14/04/21.
6+
//
7+
8+
import SwiftUI
9+
10+
/// The transition type for the whole NavigationStackView.
11+
public enum NavigationTransition {
12+
/// Transitions won't be animated.
13+
case none
14+
15+
/// Use the [default transition](x-source-tag://defaultTransition).
16+
case `default`
17+
18+
/// Use a custom transition (the transition will be applied both to push and pop operations).
19+
case custom(AnyTransition)
20+
21+
/// A right-to-left slide transition on push, a left-to-right slide transition on pop.
22+
/// - Tag: defaultTransition
23+
public static var defaultTransitions: (push: AnyTransition, pop: AnyTransition) {
24+
let pushTrans = AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))
25+
let popTrans = AnyTransition.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing))
26+
return (pushTrans, popTrans)
27+
}
28+
}
29+
30+
/// An alternative SwiftUI NavigationView implementing classic stack-based navigation giving also some more control on animations and programmatic navigation.
31+
public struct NavigationStackView<Root>: View where Root: View {
32+
@ObservedObject private var navViewModel: NavigationStack
33+
private let rootView: Root
34+
private let transitions: (push: AnyTransition, pop: AnyTransition)
35+
36+
/// Creates a NavigationStackView.
37+
/// - Parameters:
38+
/// - transitionType: The type of transition to apply between views in every push and pop operation.
39+
/// - easing: The easing function to apply to every push and pop operation.
40+
/// - rootView: The very first view in the NavigationStack.
41+
public init(transitionType: NavigationTransition = .default, easing: Animation = NavigationStack.defaultEasing, @ViewBuilder rootView: () -> Root) {
42+
self.init(transitionType: transitionType, navigationStack: NavigationStack(easing: easing), rootView: rootView)
43+
}
44+
45+
/// Creates a NavigationStackView with the provided NavigationStack
46+
/// - Parameters:
47+
/// - transitionType: The type of transition to apply between views in every push and pop operation.
48+
/// - navigationStack: the shared NavigationStack
49+
/// - rootView: The very first view in the NavigationStack.
50+
public init(transitionType: NavigationTransition = .default, navigationStack: NavigationStack, @ViewBuilder rootView: () -> Root) {
51+
self.rootView = rootView()
52+
self.navViewModel = navigationStack
53+
switch transitionType {
54+
case .none:
55+
self.transitions = (.identity, .identity)
56+
case .custom(let trans):
57+
self.transitions = (trans, trans)
58+
default:
59+
self.transitions = NavigationTransition.defaultTransitions
60+
}
61+
}
62+
63+
public var body: some View {
64+
let showRoot = navViewModel.currentView == nil
65+
let navigationType = navViewModel.navigationType
66+
67+
return ZStack {
68+
Group {
69+
if showRoot {
70+
rootView
71+
.transition(navigationType == .push ? transitions.push : transitions.pop)
72+
.environmentObject(navViewModel)
73+
} else {
74+
navViewModel.currentView!.wrappedElement
75+
.transition(navigationType == .push ? transitions.push : transitions.pop)
76+
.environmentObject(navViewModel)
77+
}
78+
}
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)