Skip to content

Commit 3bb4989

Browse files
authored
Merge pull request #22 from canopas/improve-structure
Improve structure and prepare 1.2.0
2 parents b65ee11 + 9aaeeeb commit 3bb4989

File tree

4 files changed

+57
-65
lines changed

4 files changed

+57
-65
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Once you have your Swift package set up, adding UIPilot as a dependency is as ea
3838

3939
```swift
4040
dependencies: [
41-
.package(url: "https://github.com/canopas/UIPilot.git", .upToNextMajor(from: "1.1.7"))
41+
.package(url: "https://github.com/canopas/UIPilot.git", .upToNextMajor(from: "1.2.0"))
4242
]
4343
```
4444

@@ -47,7 +47,7 @@ dependencies: [
4747
[CocoaPods][] is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate UIPilot into your Xcode project using CocoaPods, specify it in your Podfile:
4848

4949
target 'YourAppName' do
50-
pod 'UIPilot', '~> 1.1.7'
50+
pod 'UIPilot', '~> 1.2.0'
5151
end
5252

5353
[CocoaPods]: https://cocoapods.org

Sources/UIPilot/UIPilot.swift

Lines changed: 52 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,81 +3,73 @@ import Combine
33

44
public 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

181164
public typealias RouteMap<T> = (T) -> AnyView
182165

183166
public 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

UIPilot.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "UIPilot"
3-
s.version = "1.1.7"
3+
s.version = "1.2.0"
44
s.summary = "The missing type-safe, SwiftUI navigation library."
55

66
s.description = <<-DESC

docs/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ Once you have your Swift package set up, adding UIPilot as a dependency is as ea
323323

324324
```swift
325325
dependencies: [
326-
.package(url: "https://github.com/canopas/UIPilot.git", .upToNextMajor(from: "1.1.7"))
326+
.package(url: "https://github.com/canopas/UIPilot.git", .upToNextMajor(from: "1.2.0"))
327327
]
328328
```
329329

@@ -332,7 +332,7 @@ dependencies: [
332332
[CocoaPods][] is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate UIPilot into your Xcode project using CocoaPods, specify it in your Podfile:
333333

334334
target 'YourAppName' do
335-
pod 'UIPilot', '~> 1.1.7'
335+
pod 'UIPilot', '~> 1.2.0'
336336
end
337337

338338
[CocoaPods]: https://cocoapods.org

0 commit comments

Comments
 (0)