|
1 | 1 | # ReerRouter |
2 | | -App URL router for iOS (Swift only). |
| 2 | +App URL router for iOS (Swift only). Inspired by [URLNavigator](https://github.com/devxoul/URLNavigator). |
| 3 | + |
| 4 | + |
| 5 | +## Example App |
| 6 | +To run the example project, clone the repo, and run `pod install` from the Example directory first. |
| 7 | + |
| 8 | +## Requirements |
| 9 | +At least iOS 10.0 |
| 10 | +Xcode 13.2 |
| 11 | + |
| 12 | +## Installation |
| 13 | + |
| 14 | +### CocoaPods |
| 15 | +ReerRouter is available through [CocoaPods](https://cocoapods.org). To install |
| 16 | +it, simply add the following line to your Podfile: |
| 17 | + |
| 18 | +```ruby |
| 19 | +pod 'ReerRouter' |
| 20 | +``` |
| 21 | +### Swift Package Manager |
| 22 | +``` |
| 23 | +import PackageDescription |
| 24 | +
|
| 25 | +let package = Package( |
| 26 | + name: "YOUR_PROJECT_NAME", |
| 27 | + targets: [], |
| 28 | + dependencies: [ |
| 29 | + .package(url: "https://github.com/reers/ReerRouter.git", from: "0.1.0") |
| 30 | + ] |
| 31 | +) |
| 32 | +``` |
| 33 | +``` |
| 34 | +.target( |
| 35 | + name: "YOUR_TARGET_NAME", |
| 36 | + dependencies: ["ReerRouter",] |
| 37 | +), |
| 38 | +``` |
| 39 | + |
| 40 | +## Getting Started |
| 41 | +### 1. Understanding `Route.Key` |
| 42 | +`Route.Key` means URL `host` + `path` |
| 43 | +``` |
| 44 | +/// myapp://example.com/over/there?name=phoenix#nose |
| 45 | +/// \______/\_________/\_________/ \__________/ \__/ |
| 46 | +/// | | | | | |
| 47 | +/// scheme host path queries fragment |
| 48 | +/// \_________/ |
| 49 | +/// | |
| 50 | +/// route key |
| 51 | +``` |
| 52 | +### 2. Register Route |
| 53 | + |
| 54 | +* Register an action. |
| 55 | +``` |
| 56 | +Router.shared.registerAction(with: "abc_action") { _ in |
| 57 | + print("action executed.") |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +* Register a view controller by its type and a route key. |
| 62 | +``` |
| 63 | +extension Route.Key { |
| 64 | + static let userPage: Self = "user" |
| 65 | +} |
| 66 | +Router.shared.register(UserViewController.self, forKey: .userPage) |
| 67 | +Router.shared.register(UserViewController.self, forKey: "user") |
| 68 | +``` |
| 69 | + |
| 70 | +* Register view controllers by their types and route keys. |
| 71 | +``` |
| 72 | +Router.shared.registerPageClasses(with: ["preference": PreferenceViewController.self]) |
| 73 | +``` |
| 74 | + |
| 75 | +* Register view controllers by their type names and route keys. |
| 76 | +``` |
| 77 | +Router.shared.registerPageClasses(with: ["preference": "ReerRouter_Example.PreferenceViewController"]) |
| 78 | +``` |
| 79 | + |
| 80 | +* Implement `Routable` for view controller. |
| 81 | +``` |
| 82 | +class UserViewController: UIViewController, Routable { |
| 83 | + var params: [String: Any] |
| 84 | + |
| 85 | + required init?(param: Route.Param) { |
| 86 | + self.params = param.allParams |
| 87 | + super.init(nibName: nil, bundle: nil) |
| 88 | + } |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +### 3. Execute an route action. |
| 93 | +``` |
| 94 | +Router.shared.executeAction(byKey: "abc_action") |
| 95 | +Router.shared.open("myapp://abc_action") |
| 96 | +``` |
| 97 | + |
| 98 | +### 4. Open a view controller. |
| 99 | +``` |
| 100 | +Router.shared.open("myapp://user?name=phoenix") |
| 101 | +Router.shared.push("myapp://user?name=phoenix") |
| 102 | +Router.shared.present("myapp://user?name=phoenix") |
| 103 | +``` |
| 104 | + |
| 105 | +### 5. Delegate for for the app about the route. |
| 106 | +``` |
| 107 | +extension RouteManager: RouterDelegate { |
| 108 | + func router(_ router: Router, willOpenURL url: URL, userInfo: [String : Any]) -> URL? { |
| 109 | + print("will open \(url)") |
| 110 | + if let _ = url.absoluteString.range(of: "google") { |
| 111 | + return URL(string: url.absoluteString + "&extra1=234244&extra2=afsfafasd") |
| 112 | + } else if let _ = url.absoluteString.range(of: "bytedance"), !isUserLoggedIn() { |
| 113 | + print("intercepted by delegate") |
| 114 | + return nil |
| 115 | + } |
| 116 | + return url |
| 117 | + } |
| 118 | +
|
| 119 | + func router(_ router: Router, didOpenURL url: URL, userInfo: [String : Any]) { |
| 120 | + print("did open \(url) success") |
| 121 | + } |
| 122 | + |
| 123 | + func router(_ router: Router, didFailToOpenURL url: URL, userInfo: [String : Any]) { |
| 124 | + print("did fail to open \(url)") |
| 125 | + } |
| 126 | + |
| 127 | + func router(_ router: Router, didFallbackToURL url: URL, userInfo: [String: Any]) { |
| 128 | + print("did fallback to \(url)") |
| 129 | + } |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +### 6. Fallback |
| 134 | +* Use `route_fallback_url` key as a fallback url when some thing went wrong. |
| 135 | +``` |
| 136 | +Router.shared.open("myapp://unregisteredKey?route_fallback_url=myapp%3A%2F%2Fuser%3Fname%3Di_am_fallback") |
| 137 | +``` |
| 138 | + |
| 139 | +### 7. Redirect |
| 140 | +* Implement `redirectURLWithRouteParam(_:)` method to redirect to a new url for the view controller. |
| 141 | +``` |
| 142 | +class PreferenceViewController: UIViewController, Routable { |
| 143 | + |
| 144 | + required init?(param: Route.Param) { |
| 145 | + super.init(nibName: nil, bundle: nil) |
| 146 | + } |
| 147 | + |
| 148 | + class func redirectURLWithRouteParam(_ param: Route.Param) -> URL? { |
| 149 | + if let value = param.allParams["some_key"] as? String, value == "redirect" { |
| 150 | + return URL(string: "myapp://new_preference") |
| 151 | + } |
| 152 | + return nil |
| 153 | + } |
| 154 | +} |
| 155 | +``` |
| 156 | + |
| 157 | +### 8. Global instance for the router singleton. |
| 158 | +``` |
| 159 | +public let AppRouter = Router.shared |
| 160 | +AppRouter.open("myapp://user") |
| 161 | +``` |
| 162 | + |
| 163 | +### 9. Notifications when will open and did open. |
| 164 | +``` |
| 165 | +NotificationCenter.default.addObserver( |
| 166 | + forName: Notification.Name.routeWillOpenURL, |
| 167 | + object: nil, |
| 168 | + queue: .main |
| 169 | +) { notification in |
| 170 | + if let param = notification.userInfo?[Route.notificationUserInfoKey] as? Route.Param { |
| 171 | + print("notification: route will open \(param.sourceURL)") |
| 172 | + } |
| 173 | +} |
| 174 | +
|
| 175 | +NotificationCenter.default.addObserver( |
| 176 | + forName: Notification.Name.routeDidOpenURL, |
| 177 | + object: nil, |
| 178 | + queue: .main |
| 179 | +) { notification in |
| 180 | + if let param = notification.userInfo?[Route.notificationUserInfoKey] as? Route.Param { |
| 181 | + print("notification: route did open \(param.sourceURL)") |
| 182 | + } |
| 183 | +} |
| 184 | +``` |
| 185 | + |
| 186 | +### 10. Custom controlling for transition. |
| 187 | +``` |
| 188 | +public typealias UserTransition = ( |
| 189 | + _ fromNavigationController: UINavigationController?, |
| 190 | + _ fromViewController: UIViewController?, |
| 191 | + _ toViewController: UIViewController |
| 192 | +) -> Bool |
| 193 | +
|
| 194 | +public enum TransitionExecutor { |
| 195 | + /// Transition will be handled by router automatically. |
| 196 | + case router |
| 197 | + /// Transition will be handled by user who invoke the router `push` or `present` method. |
| 198 | + case user(UserTransition) |
| 199 | + /// Transition will be handled by user who invoke the router `push` or `present` method. |
| 200 | + case delegate |
| 201 | +} |
| 202 | +
|
| 203 | +let transition: Route.UserTransition = { fromNavigationController, fromViewController, toViewController in |
| 204 | + toViewController.transitioningDelegate = self.animator |
| 205 | + toViewController.modalPresentationStyle = .currentContext |
| 206 | + // Use the router found view controller directly, or just handle transition by yourself. |
| 207 | + // fromViewController?.present(toViewController, animated: true) |
| 208 | + self.present(toViewController, animated: true) |
| 209 | + return true |
| 210 | +} |
| 211 | +AppRouter.present(user.urlString, transitionExecutor: .user(transition)) |
| 212 | +``` |
| 213 | + |
| 214 | +## Author |
| 215 | + |
| 216 | +phoenix, x.rhythm@qq.com |
| 217 | + |
| 218 | +## License |
| 219 | + |
| 220 | +ReerRouter is available under the MIT license. See the LICENSE file for more info. |
0 commit comments