Skip to content

Commit 9771982

Browse files
modernize
1 parent 910b4c6 commit 9771982

38 files changed

Lines changed: 685 additions & 166 deletions

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
branches: [master, main]
8+
9+
jobs:
10+
build-and-test:
11+
runs-on: macos-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Show Xcode version
16+
run: xcodebuild -version
17+
18+
- name: Build and test
19+
run: |
20+
xcodebuild \
21+
-project XCoordinator-Example.xcodeproj \
22+
-scheme XCoordinator-Example \
23+
-testPlan XCoordinator-Example \
24+
-destination 'platform=iOS Simulator,OS=latest,name=iPhone 16' \
25+
CODE_SIGNING_ALLOWED=NO \
26+
test

.gitignore

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
# Xcode
2-
#
3-
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
42

53
## Build generated
64
build/
@@ -16,6 +14,10 @@ DerivedData/
1614
*.perspectivev3
1715
!default.perspectivev3
1816
xcuserdata/
17+
*.xcworkspace/xcuserdata/
18+
19+
## macOS
20+
.DS_Store
1921

2022
## Other
2123
*.moved-aside
@@ -32,37 +34,11 @@ xcuserdata/
3234
timeline.xctimeline
3335
playground.xcworkspace
3436

35-
# Swift Package Manager
37+
## Swift Package Manager
3638
#
3739
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
3840
# Packages/
3941
# Package.pins
4042
# Package.resolved
4143
.build/
42-
43-
# CocoaPods
44-
#
45-
# We recommend against adding the Pods directory to your .gitignore. However
46-
# you should judge for yourself, the pros and cons are mentioned at:
47-
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48-
#
49-
# Pods/
50-
51-
# Carthage
52-
#
53-
# Add this line if you want to avoid checking in source code from Carthage dependencies.
54-
# Carthage/Checkouts
55-
56-
Carthage/Build
57-
58-
# fastlane
59-
#
60-
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61-
# screenshots whenever they are needed.
62-
# For more information about the recommended setup visit:
63-
# https://docs.fastlane.tools/best-practices/source-control/#source-control
64-
65-
fastlane/report.xml
66-
fastlane/Preview.html
67-
fastlane/screenshots/**/*.png
68-
fastlane/test_output
44+
.swiftpm/

.swift-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
5.9

README.md

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,112 @@
1-
21
<p align="center">
32
<img src="https://quickbirdstudios.com/files/xcoordinator/logo.png">
43
</p>
54

65
# XCoordinator-Example
76

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.
9101

10102
## 👤 Author
103+
11104
This example app is created with ❤️ by [QuickBird Studios](https://quickbirdstudios.com).
12105

13106
## ❤️ Contributing
14107

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.
18109

19110
## 📃 License
20111

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

Comments
 (0)