|
1 | 1 | // |
2 | | -// Router.swift |
| 2 | +// AppRoute.swift |
3 | 3 | // GiphyGIF |
4 | 4 | // |
5 | 5 | // Created by Uwais Alqadri on 10/10/24. |
6 | 6 | // |
7 | 7 |
|
8 | 8 | import Foundation |
9 | | -import SwiftUI |
10 | | - |
11 | | -public protocol RouterIdentifiable: Equatable { |
12 | | - var key: String { get } |
13 | | -} |
14 | | - |
15 | | -public final class Router<T: RouterIdentifiable> { |
16 | | - private var routes: [T] = [] |
17 | | - public var onMakeRoot: ((T, Bool) -> Void)? |
18 | | - public var onPush: ((T, Bool) -> Void)? |
19 | | - public var onPresent: ((T, Bool) -> Void)? |
20 | | - public var onPopLast: ((Int, Bool) -> Void)? |
21 | | - public var onPopToRoot: ((Int?, Bool) -> Void)? |
22 | | - |
23 | | - public var currentRoutes: [T] { |
24 | | - return routes |
25 | | - } |
26 | | - |
27 | | - public init(initial: T? = nil) { |
28 | | - if let initial = initial { push(initial) } |
29 | | - } |
30 | | - |
31 | | - public func makeRoot(_ route: T, animated: Bool = true, needValidate: Bool = false) { |
32 | | - guard !(needValidate && routes.last?.key == route.key) else { return } |
33 | | - routes = [route] |
34 | | - onMakeRoot?(route, animated) |
35 | | - } |
36 | | - |
37 | | - public func push(_ route: T, animated: Bool = true, needValidate: Bool = false) { |
38 | | - guard !(needValidate && routes.last?.key == route.key) else { return } |
39 | | - routes.append(route) |
40 | | - onPush?(route, animated) |
41 | | - } |
42 | | - |
43 | | - public func present(_ route: T, animated: Bool = true, needValidate: Bool = false) { |
44 | | - guard !(needValidate && routes.last?.key == route.key) else { return } |
45 | | - routes.append(route) |
46 | | - onPresent?(route, animated) |
47 | | - } |
48 | | - |
49 | | - public func pop(animated: Bool = true) { |
50 | | - guard !routes.isEmpty else { return } |
51 | | - let popped = routes.removeLast() |
52 | | - print("Router popped: \(popped)") |
53 | | - onPopLast?(1, animated) |
54 | | - } |
55 | | - |
56 | | - public func popTo(last index: Int, animated: Bool = true) { |
57 | | - guard !routes.isEmpty else { return } |
58 | | - let elementsToRemove = min(index - 1, routes.count - 1) |
59 | | - routes.removeLast(elementsToRemove) |
60 | | - onPopLast?(index, animated) |
61 | | - } |
62 | | - |
63 | | - public func popTo(_ route: T, inclusive: Bool = false, animated: Bool = true) { |
64 | | - guard let foundIndex = routes.lastIndex(where: { $0 == route }) else { return } |
65 | | - let indexToPopTo = inclusive ? foundIndex : foundIndex + 1 |
66 | | - guard indexToPopTo != 0 else { |
67 | | - popToRoot(index: nil, animated: animated) |
68 | | - return |
69 | | - } |
70 | | - |
71 | | - let numToPop = routes.count - indexToPopTo |
72 | | - routes.removeLast(numToPop) |
73 | | - onPopLast?(numToPop, animated) |
74 | | - } |
75 | | - |
76 | | - public func popToRoot(index: Int? = nil, animated: Bool = true) { |
77 | | - onPopToRoot?(index, animated) |
78 | | - if routes.count > 1 { |
79 | | - routes.removeSubrange(1 ..< routes.count) |
80 | | - } |
81 | | - } |
82 | | - |
83 | | - public func onSystemPop() { |
84 | | - guard !routes.isEmpty else { return } |
85 | | - let popped = routes.removeLast() |
86 | | - print("Router popped: \(popped)") |
87 | | - } |
88 | | -} |
89 | | - |
90 | | -public struct RouteProvider<T: RouterIdentifiable, Screen: View>: View { |
91 | | - private let router: Router<T> |
92 | | - @ViewBuilder private let routeMap: (T) -> Screen |
93 | | - |
94 | | - public init(_ router: Router<T>, @ViewBuilder _ routeMap: @escaping (T) -> Screen) { |
95 | | - self.router = router |
96 | | - self.routeMap = routeMap |
97 | | - } |
98 | | - |
99 | | - public var body: some View { |
100 | | - NavigationControllerHost(router: router, routeMap: routeMap) |
101 | | - .edgesIgnoringSafeArea(.all) |
102 | | - } |
103 | | -} |
104 | | - |
105 | | -struct NavigationControllerHost<T: RouterIdentifiable, Screen: View>: UIViewControllerRepresentable { |
106 | | - let router: Router<T> |
107 | | - @ViewBuilder var routeMap: (T) -> Screen |
108 | | - |
109 | | - func makeUIViewController(context: Context) -> PopAwareUINavigationController { |
110 | | - let navigation = PopAwareUINavigationController() |
111 | | - navigation.navigationBar.isHidden = true |
112 | | - setupRouterCallbacks(in: navigation) |
113 | | - setupInitialRoutes(in: navigation) |
114 | | - return navigation |
115 | | - } |
116 | | - |
117 | | - private func setupRouterCallbacks(in navigation: PopAwareUINavigationController) { |
118 | | - navigation.popHandler = { router.onSystemPop() } |
119 | | - navigation.stackSizeProvider = { router.currentRoutes.count } |
120 | | - |
121 | | - router.onMakeRoot = { route, animated in |
122 | | - let viewController = UIHostingController(rootView: routeMap(route)) |
123 | | - navigation.setViewControllers([viewController], animated: animated) |
124 | | - } |
125 | | - |
126 | | - router.onPush = { route, animated in |
127 | | - let viewController = UIHostingController(rootView: routeMap(route)) |
128 | | - navigation.pushViewController(viewController, animated: animated) |
129 | | - } |
130 | | - |
131 | | - router.onPresent = { route, animated in |
132 | | - let viewController = UIHostingController(rootView: routeMap(route)) |
133 | | - viewController.modalPresentationStyle = .overFullScreen |
134 | | - navigation.present(viewController, animated: animated) |
135 | | - } |
136 | | - |
137 | | - router.onPopLast = { numToPop, animated in |
138 | | - let popTo = navigation.viewControllers.count - numToPop - 1 |
139 | | - if numToPop == navigation.viewControllers.count { |
140 | | - navigation.viewControllers = [] |
141 | | - } else { |
142 | | - navigation.popToViewController(navigation.viewControllers[popTo], animated: animated) |
143 | | - } |
144 | | - } |
145 | | - |
146 | | - router.onPopToRoot = { tabIndex, animated in |
147 | | - navigation.popToRootViewController(animated: animated) |
148 | | - if let tabIndex = tabIndex { |
149 | | - NotificationCenter.default.post( |
150 | | - name: NSNotification.Name("shouldOpenTab"), |
151 | | - object: nil, |
152 | | - userInfo: ["tabIndex": tabIndex] |
153 | | - ) |
154 | | - } |
155 | | - } |
156 | | - } |
157 | | - |
158 | | - private func setupInitialRoutes(in navigation: PopAwareUINavigationController) { |
159 | | - for path in router.currentRoutes { |
160 | | - navigation.pushViewController(UIHostingController(rootView: routeMap(path)), animated: true) |
| 9 | +import Common |
| 10 | + |
| 11 | +public enum AppRoute: RouterIdentifiable { |
| 12 | + case main |
| 13 | + case home |
| 14 | + case search |
| 15 | + case favorite |
| 16 | + case detail(items: [Giffy]) |
| 17 | + |
| 18 | + public var key: String { |
| 19 | + switch self { |
| 20 | + case .main: |
| 21 | + return "main" |
| 22 | + case .home: |
| 23 | + return "main" |
| 24 | + case .search: |
| 25 | + return "search" |
| 26 | + case .favorite: |
| 27 | + return "favorite" |
| 28 | + case .detail: |
| 29 | + return "detail" |
161 | 30 | } |
162 | 31 | } |
163 | | - |
164 | | - func updateUIViewController(_ navigation: PopAwareUINavigationController, context: Context) { |
165 | | - navigation.navigationBar.isHidden = true |
166 | | - } |
167 | | - |
168 | | - static func dismantleUIViewController(_ navigation: PopAwareUINavigationController, coordinator: ()) { |
169 | | - navigation.viewControllers = [] |
170 | | - navigation.popHandler = nil |
171 | | - } |
172 | | - |
173 | | - typealias UIViewControllerType = PopAwareUINavigationController |
174 | 32 | } |
175 | 33 |
|
176 | | -class PopAwareUINavigationController: UINavigationController, UINavigationControllerDelegate { |
177 | | - var popHandler: (() -> Void)? |
178 | | - var stackSizeProvider: (() -> Int)? |
| 34 | +@propertyWrapper |
| 35 | +public struct Router { |
| 36 | + public init() {} |
179 | 37 |
|
180 | | - override func viewDidLoad() { |
181 | | - super.viewDidLoad() |
182 | | - delegate = self |
183 | | - } |
| 38 | + private var customValue: Routing<AppRoute>? |
| 39 | + public static var defaultRouter: Routing<AppRoute> = Routing() |
184 | 40 |
|
185 | | - func navigationController(_ navigationController: UINavigationController, didShow _: UIViewController, animated _: Bool) { |
186 | | - if let stackSizeProvider = stackSizeProvider, stackSizeProvider() > navigationController.viewControllers.count { |
187 | | - popHandler?() |
188 | | - } |
| 41 | + public var wrappedValue: Routing<AppRoute> { |
| 42 | + get { customValue ?? Self.defaultRouter } |
| 43 | + set { customValue = newValue } |
189 | 44 | } |
190 | 45 | } |
0 commit comments