@@ -3,81 +3,73 @@ import Combine
33
44public class UIPilot < T: Equatable > : ObservableObject {
55
6- let logger : Logger
7- var state : UIPilotViewState < T > !
6+ private let logger : Logger
7+ private let viewGenerator = PathViewGenerator < T > ( )
88
9- var paths : [ Path < T > ] = [ ] {
10- didSet { updateViewState ( ) }
11- }
9+ @Published var paths : [ UIPilotPath < T > ] = [ ]
1210
13- var routeMap : RouteMap < T > ? {
14- didSet { updateViewState ( ) }
15- }
16-
1711 public var stack : [ T ] {
1812 return paths. map { $0. route }
1913 }
20-
14+
2115 public init ( initial: T , debug: Bool = false ) {
2216 logger = debug ? DebugLog ( ) : EmptyLog ( )
2317 logger. log ( " UIPilot - Pilot Initialized. " )
2418
25- state = UIPilotViewState ( onPop : { [ weak self] in
19+ viewGenerator . onPop = { [ weak self] in
2620 self ? . pop ( )
27- } )
21+ }
22+
2823 push ( initial)
2924 }
30-
25+
3126 public func push( _ route: T ) {
3227 logger. log ( " UIPilot - Pushing \( route) route. " )
33- self . paths. append ( Path ( route: route) )
28+ self . paths. append ( UIPilotPath ( route: route) )
3429 }
35-
30+
3631 public func pop( ) {
3732 if !self . paths. isEmpty {
3833 logger. log ( " UIPilot - Route popped. " )
3934 self . paths. removeLast ( )
4035 }
4136 }
42-
37+
4338 public func popTo( _ route: T , inclusive: Bool = false ) {
4439 logger. log ( " UIPilot: Popping route \( route) . " )
4540
4641 if paths. isEmpty {
4742 logger. log ( " UIPilot - Path is empty. " )
4843 return
4944 }
50-
45+
5146 guard var found = paths. firstIndex ( where: { $0. route == route } ) else {
5247 logger. log ( " UIPilot - Route not found. " )
5348 return
5449 }
55-
50+
5651 if !inclusive {
5752 found += 1
5853 }
59-
54+
6055 let numToPop = ( found..< paths. endIndex) . count
6156 logger. log ( " UIPilot - Popping \( numToPop) routes " )
6257 paths. removeLast ( numToPop)
6358 }
64-
65- private func updateViewState( ) {
66- if let routeMap = routeMap {
67- logger. log ( " UIPilot - Updating route state. " )
68- state. onPathsChanged ( paths: paths, routeMap: routeMap)
69- }
59+
60+ func getView( _ paths: [ UIPilotPath < T > ] , _ routeMap: RouteMap < T > , _ pathViews: [ UIPilotPath < T > : PathView ] ) -> ( PathView ? , [ UIPilotPath < T > : PathView ] ) {
61+ return viewGenerator. generate ( paths, routeMap, pathViews)
7062 }
7163}
7264
73- struct Path < T: Equatable > : Equatable , Hashable {
65+ struct UIPilotPath < T: Equatable > : Equatable , Hashable {
7466 let route : T
7567 let id : String = UUID ( ) . uuidString
76-
77- static func == ( lhs: Path , rhs: Path ) -> Bool {
68+
69+ static func == ( lhs: UIPilotPath , rhs: UIPilotPath ) -> Bool {
7870 return lhs. route == rhs. route && lhs. id == rhs. id
7971 }
80-
72+
8173 func hash( into hasher: inout Hasher ) {
8274 hasher. combine ( id)
8375 }
@@ -114,93 +106,93 @@ class PathViewState: ObservableObject {
114106 }
115107 }
116108 }
117-
109+
118110 @Published
119- var next : PathView ? = nil {
111+ var next : PathView ? {
120112 didSet {
121113 isActive = next != nil
122114 }
123115 }
124-
116+
125117 var onPop : ( ) -> Void
126-
118+
127119 init ( next: PathView ? = nil , onPop: @escaping ( ) -> Void = { } ) {
128120 self . next = next
129121 self . onPop = onPop
130122 }
131123}
132124
133- class UIPilotViewState < T: Equatable > : ObservableObject {
134-
135- private let onPop : ( ) -> Void
136- private var pathViews = [ Path < T > : PathView ] ( )
137-
138- @Published var content : PathView ? = nil
125+ class PathViewGenerator < T: Equatable > {
139126
140- init ( onPop: @escaping ( ) -> Void ) {
141- self . onPop = onPop
142- }
143-
144- func onPathsChanged( paths: [ Path < T > ] , routeMap: RouteMap < T > ) {
145- content = getView ( paths, routeMap)
146- }
127+ var onPop : ( ( ) -> Void ) ?
147128
148- func getView ( _ paths: [ Path < T > ] , _ routeMap: RouteMap < T > ) -> PathView ? {
149- recycleViews ( paths)
129+ func generate ( _ paths: [ UIPilotPath < T > ] , _ routeMap: RouteMap < T > , _ pathViews : [ UIPilotPath < T > : PathView ] ) -> ( PathView ? , [ UIPilotPath < T > : PathView ] ) {
130+ var pathViews = recycleViews ( paths, pathViews : pathViews )
150131
151- var current : PathView ? = nil
132+ var current : PathView ?
152133 for path in paths. reversed ( ) {
153134 var content = pathViews [ path]
154135
155136 if content == nil {
156137 pathViews [ path] = PathView ( routeMap ( path. route) , state: PathViewState ( ) )
157138 content = pathViews [ path]
158139 }
159-
140+
160141 content? . state. next = current
161142 content? . state. onPop = current == nil ? { } : { [ weak self] in
162143 if let self = self , !paths. isEmpty,
163144 paths. last != path {
164- self . onPop ( )
145+ self . onPop ? ( )
165146 }
166147 }
167148 current = content
168149 }
169- return current
150+ return ( current, pathViews )
170151 }
171-
172- private func recycleViews( _ paths: [ Path < T > ] ) {
152+
153+ private func recycleViews( _ paths: [ UIPilotPath < T > ] , pathViews: [ UIPilotPath < T > : PathView ] ) -> [ UIPilotPath < T > : PathView ] {
154+ var pathViews = pathViews
173155 for key in pathViews. keys {
174156 if !paths. contains ( key) {
175157 pathViews. removeValue ( forKey: key)
176158 }
177159 }
160+ return pathViews
178161 }
179162}
180163
181164public typealias RouteMap < T> = ( T ) -> AnyView
182165
183166public struct UIPilotHost < T: Equatable > : View {
184167
185- private let pilot : UIPilot < T >
186-
187168 @ObservedObject
188- private var state : UIPilotViewState < T >
169+ private var pilot : UIPilot < T >
170+
171+ private let routeMap : RouteMap < T >
172+
173+ @State
174+ var pathViews = [ UIPilotPath < T > : PathView ] ( )
175+ @State
176+ var content : PathView ?
189177
190178 public init ( _ pilot: UIPilot < T > , _ routeMap: @escaping RouteMap < T > ) {
191179 self . pilot = pilot
192- self . state = pilot. state
193- self . pilot. routeMap = routeMap
180+ self . routeMap = routeMap
194181 }
195182
196183 public var body : some View {
197184 NavigationView {
198- state . content
185+ content
199186 }
200187#if !os(macOS)
201188 . navigationViewStyle( . stack)
202189#endif
203190 . environmentObject( pilot)
191+ . onReceive ( pilot. $paths) { paths in
192+ let ( newContent, newPathViews) = pilot. getView ( paths, routeMap, pathViews)
193+ self . content = newContent
194+ self . pathViews = newPathViews
195+ }
204196 }
205197}
206198
0 commit comments