|
1 | | - |
2 | 1 | <p align="center"> |
3 | 2 | <img src="https://quickbirdstudios.com/files/xcoordinator/logo.png"> |
4 | 3 | </p> |
5 | 4 |
|
6 | 5 | # XCoordinator-Example |
7 | 6 |
|
8 | | -XCoordinator-Example is a MVVM-C example app for [XCoordinator](https://github.com/quickbirdstudios/XCoordinator). For a MVC example app, have a look at [a workshop](https://github.com/quickbirdstudios/Mobile-HackNight-XCoordinator) we did with a previous version of XCoordinator. |
| 7 | +A sample iOS app showing **MVVM-C with [XCoordinator](https://github.com/quickbirdstudios/XCoordinator) v2** — built specifically to demonstrate the library's container coordinators side by side and to give you a worked example of MVVM-C scene wiring with RxSwift. |
| 8 | + |
| 9 | +<!-- |
| 10 | +TODO: record and commit a hero GIF at docs/hero.gif showing |
| 11 | +the picker alert → same HomeRoute.news flow rendered three times |
| 12 | +under HomeTabCoordinator, HomeSplitCoordinator, HomePageCoordinator. |
| 13 | +Then uncomment the line below. |
| 14 | +
|
| 15 | +<p align="center"><img src="docs/hero.gif" width="600" alt="Demo"></p> |
| 16 | +--> |
| 17 | + |
| 18 | +## What this example teaches |
| 19 | + |
| 20 | +A login → home → detail flow built end-to-end with XCoordinator v2 and MVVM-C. Use it as a reference for: |
| 21 | + |
| 22 | +- **Four coordinator container types in one app** — `NavigationCoordinator`, `TabBarCoordinator`, `SplitCoordinator`, and `PageCoordinator`. The Home screen lets you pick which one drives the same `HomeRoute`, so you can compare them side-by-side. |
| 23 | +- **MVVM-C scene wiring** via the `BindableType` protocol — each scene is a `ViewController` + `ViewModel` protocol triplet + `ViewModelImpl`, bound with [RxSwift](https://github.com/ReactiveX/RxSwift) and [Action](https://github.com/RxSwiftCommunity/Action). |
| 24 | +- **Custom transition animations** — a swirl push, fade modals, and shared defaults under `Animations/`. |
| 25 | +- **Deep linking** through nested coordinators with `Transition.multiple(.dismissAll(), .popToRoot(), deepLink(...))`, simulating an incoming push notification. |
| 26 | +- **Child coordinators without a transition** — the About screen is attached via `addChild` + `.none()` instead of being presented. |
| 27 | +- **Custom transition extensions** — `presentFullScreen` and `dismissAll` in `Extensions/Transitions.swift`. |
| 28 | + |
| 29 | +## The three-home-coordinator picker |
| 30 | + |
| 31 | +When you reach the Home screen, the app asks you to pick between `HomeTabCoordinator`, `HomeSplitCoordinator`, `HomePageCoordinator`, or a random one. This picker is the point of the example: all three coordinators expose the **same** `HomeRoute { case news; case userList }`, and `AppCoordinator` wraps whichever one you pick into `AppRoute.home(StrongRouter<HomeRoute>)`. |
| 32 | + |
| 33 | +The takeaway: routes describe *what* should happen, coordinators decide *how*. Swapping the container coordinator changes the entire navigation chrome of your app without touching a single view model or route definition. Open [`AppCoordinator.swift`](XCoordinator-Example/Coordinators/AppCoordinator.swift) to see the wrapping, then compare [`HomeTabCoordinator.swift`](XCoordinator-Example/Coordinators/HomeTabCoordinator.swift), [`HomeSplitCoordinator.swift`](XCoordinator-Example/Coordinators/HomeSplitCoordinator.swift), and [`HomePageCoordinator.swift`](XCoordinator-Example/Coordinators/HomePageCoordinator.swift) to see three different `prepareTransition(for:)` implementations handling identical routes. |
| 34 | + |
| 35 | +## Deep-link demo |
| 36 | + |
| 37 | +The app registers the `xcoordinator-example://` URL scheme. Trigger it from a booted simulator: |
| 38 | + |
| 39 | +```bash |
| 40 | +xcrun simctl openurl booted "xcoordinator-example://news/3" |
| 41 | +xcrun simctl openurl booted "xcoordinator-example://users/Paul" |
| 42 | +``` |
| 43 | + |
| 44 | +`SceneDelegate.scene(_:openURLContexts:)` (and the matching cold-launch path in `willConnectTo`) parses the URL via [`DeepLinkParser`](XCoordinator-Example/Common/DeepLinkParser.swift) into an `AppRoute`, then triggers it on the app's `StrongRouter<AppRoute>`. For news, `AppRoute.newsDetail` resolves to |
| 45 | + |
| 46 | +```swift |
| 47 | +.multiple( |
| 48 | + .dismissAll(), |
| 49 | + .popToRoot(), |
| 50 | + deepLink(AppRoute.home(...), HomeRoute.news, NewsRoute.newsDetail(news)) |
| 51 | +) |
| 52 | +``` |
| 53 | + |
| 54 | +`.multiple` chains the transitions sequentially, and `deepLink(...)` walks the coordinator hierarchy by triggering successive routes, so the app lands on the article from *any* current navigation state (modal stacks included). The users path (`AppRoute.userDetail`) takes the same shape and ends in a modal present. |
| 55 | + |
| 56 | +One gotcha worth knowing if you deep-link through a `PageCoordinator`: `deepLink` chains the next route inside each transition's completion handler, but `UIPageViewController.setViewControllers(…, animated: true)` silently skips its completion when the requested page is *already* on-screen. A deep link whose page step targets the already-visible page would stall there. This example works around it with `Transition.setReliably(_:direction:)` in [`Extensions/Transitions.swift`](XCoordinator-Example/Extensions/Transitions.swift), a drop-in replacement for `.set` that always fires the completion — see [`HomePageCoordinator`](XCoordinator-Example/Coordinators/HomePageCoordinator.swift). |
| 57 | + |
| 58 | +## Requirements |
| 59 | + |
| 60 | +- Xcode (current stable) |
| 61 | +- Swift 5.9 |
| 62 | +- iOS 16+ (iPhone and iPad) |
| 63 | +- Swift Package Manager — `XCoordinator` 2.2.1, `RxSwift` 6.10.2, `Action` 5.0.0 |
| 64 | + |
| 65 | +No CocoaPods, no Carthage; dependencies resolve automatically when you open the project. |
| 66 | + |
| 67 | +## Run it |
| 68 | + |
| 69 | +```bash |
| 70 | +git clone https://github.com/quickbirdstudios/XCoordinator-Example.git |
| 71 | +cd XCoordinator-Example |
| 72 | +open XCoordinator-Example.xcodeproj |
| 73 | +``` |
| 74 | + |
| 75 | +Build and run on any iOS 16+ simulator. Log in with any credentials, then pick a Home coordinator from the alert to start exploring. |
| 76 | + |
| 77 | +## Project structure |
| 78 | + |
| 79 | +``` |
| 80 | +XCoordinator-Example/ |
| 81 | +├── Common/ AppDelegate, SceneDelegate, asset catalog, launch screen |
| 82 | +├── Coordinators/ AppCoordinator + four home coordinators + News/User/UserList/About |
| 83 | +├── Scenes/<Feature>/ |
| 84 | +│ ├── <Feature>ViewController.swift (xib-based, conforms to BindableType) |
| 85 | +│ ├── <Feature>ViewModel.swift (Input / Output / composite protocols) |
| 86 | +│ └── <Feature>ViewModelImpl.swift (RxSwift + Action implementation) |
| 87 | +├── Animations/ Custom Animation instances (.fade, .scale, .swirl, .modal, .navigation) |
| 88 | +├── Extensions/ Transitions.swift (presentFullScreen, dismissAll), Rx helpers |
| 89 | +├── Services/ MockNewsService, MockUserService (no real backend) |
| 90 | +├── Utils/ BindableType, NibIdentifiable |
| 91 | +└── Models/ News, User |
| 92 | +``` |
| 93 | + |
| 94 | +Every scene under `Scenes/<Feature>/` is the same triplet pattern — read [`HomeViewModel.swift`](XCoordinator-Example/Scenes/Home/HomeViewModel.swift) once to see how Input/Output/composite protocols compose into a single `ViewModelImpl`. |
| 95 | + |
| 96 | +## Learn more |
| 97 | + |
| 98 | +- [XCoordinator](https://github.com/quickbirdstudios/XCoordinator) — the library this example demonstrates. |
| 99 | +- [Mobile-HackNight-XCoordinator](https://github.com/quickbirdstudios/Mobile-HackNight-XCoordinator) — a workshop using an earlier version of XCoordinator with MVC. |
| 100 | +- [Coordinators Redux](https://khanlou.com/2015/10/coordinators-redux/) by Soroush Khanlou — background reading on the coordinator pattern. |
9 | 101 |
|
10 | 102 | ## 👤 Author |
| 103 | + |
11 | 104 | This example app is created with ❤️ by [QuickBird Studios](https://quickbirdstudios.com). |
12 | 105 |
|
13 | 106 | ## ❤️ Contributing |
14 | 107 |
|
15 | | -Open an issue if you need help, if you found a bug, or if you want to discuss a feature request. If you feel like having a chat about XCoordinator or this example app with the developers and other users, join our [Slack Workspace](https://join.slack.com/t/xcoordinator/shared_invite/enQtNDg4NDAxNTk1ODQ1LTRhMjY0OTAwNWMyYmQ5ZWI5Mzk3ODU1NGJmMWZlZDY3Y2Q0NTZjOWNkMjgyNmQwYjY4MzZmYTRhN2EzMzczNTM). |
16 | | - |
17 | | -Open a PR if you want to make changes to the XCoordinator example app. |
| 108 | +Open an issue if you need help, if you found a bug, or if you want to discuss a feature request. Open a PR if you want to make changes to the XCoordinator example app. |
18 | 109 |
|
19 | 110 | ## 📃 License |
20 | 111 |
|
21 | | -XCoordinator-Example is released under an MIT license. See [License.md](https://github.com/quickbirdstudios/XCoordinator-Example/blob/master/LICENSE) for more information. |
| 112 | +XCoordinator-Example is released under an MIT license. See [LICENSE](LICENSE) for more information. |
0 commit comments