Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/felilo/suicoordinator
Navigation coordinators for SWiftUI. Simple, powerful and elegant.
https://github.com/felilo/suicoordinator
architecture-components composition-root coordinator-pattern mvvm mvvm-c navigation-stack swift swiftui viper
Last synced: about 2 months ago
JSON representation
Navigation coordinators for SWiftUI. Simple, powerful and elegant.
- Host: GitHub
- URL: https://github.com/felilo/suicoordinator
- Owner: felilo
- Created: 2024-01-25T20:23:24.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2024-08-17T22:50:33.000Z (5 months ago)
- Last Synced: 2024-10-30T17:25:11.890Z (2 months ago)
- Topics: architecture-components, composition-root, coordinator-pattern, mvvm, mvvm-c, navigation-stack, swift, swiftui, viper
- Language: Swift
- Homepage:
- Size: 208 KB
- Stars: 51
- Watchers: 0
- Forks: 5
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# SUICoordinator
This repository contains a library that implements the Coordinator pattern, which is a design pattern used in iOS application development to manage application navigation flows. The library provides a set of features that can be used to implement the Coordinators flow. This library does not use any UIKit components.
_____## Getting Started
To use the SUICoordinator library in your iOS project, you'll need to add the library files to your project. Here are the basic steps:
_____## Defining the coordinator
First let's define the paths and views.
```swift
import SwiftUI
import SUICoordinatorenum HomeRoute: RouteType {
case push(viewModel: PushViewModel)
case sheet(viewModel: SheetViewModel)
case fullscreen(viewModel: FullscreenViewModel)
case detents(viewModel: DetentsViewModel)
case actionListView(viewModel: ActionListViewModel)
var presentationStyle: TransitionPresentationStyle {
switch self {
case .push:
return .push
case .sheet:
return .sheet
case .fullscreen:
return .fullScreenCover
case .detents:
return .detents([.medium])
case .actionListView:
return .push
}
}
var view: Body {
switch self {
case .push(let viewModel):
return PushView(viewModel: viewModel)
case .sheet(let viewModel):
return SheetView(viewModel: viewModel)
case .fullscreen(let viewModel):
return FullscreenView(viewModel: viewModel)
case .detents(let viewModel):
return DetentsView(viewModel: viewModel)
case .actionListView(let viewModel):
return NavigationActionListView(viewModel: viewModel)
}
}
}
```Second let's create the first Coordinator. All coordinator should to implement the ``start()`` function and then starts the flow (mandatory). Finally, add additional flows
```swift
import SUICoordinatorclass HomeCoordinator: Coordinator {
override func start(animated: Bool = true) async {
let viewModel = ActionListViewModel(coordinator: self)
await startFlow(route: .actionListView(viewModel: viewModel), animated: animated)
}
func navigateToPushView() async {
let viewModel = PushViewModel(coordinator: self)
await router.navigate(to: .push(viewModel: viewModel))
}
func presentSheet() async {
let viewModel = SheetViewModel(coordinator: self)
await router.navigate(to: .sheet(viewModel: viewModel))
}
func presentFullscreen() async {
let viewModel = FullscreenViewModel(coordinator: self)
await router.navigate(to: .fullscreen(viewModel: viewModel))
}
func presentDetents() async {
let viewModel = DetentsViewModel(coordinator: self)
await router.navigate(to: .detents(viewModel: viewModel))
}
func presentTabbarCoordinator() async {
let coordinator = TabbarFlowCoordinator()
await navigate(to: coordinator, presentationStyle: .sheet)
}
func close() async {
await router.close()
}
func finsh() async {
await finishFlow(animated: true)
}
}
```Then let's create a View and its ViewModel
```swift
import Foundationclass ActionListViewModel: ObservableObject {
let coordinator: HomeCoordinator
init(coordinator: HomeCoordinator) {
self.coordinator = coordinator
}
func navigateToPushView() async {
await coordinator.navigateToPushView()
}
func presentSheet() async {
await coordinator.presentSheet()
}
func presentFullscreen() async {
await coordinator.presentFullscreen()
}
func presentDetents() async {
await coordinator.presentDetents()
}
func presentTabbarCoordinator() async {
await coordinator.presentTabbarCoordinator()
}
func finsh() async {
await coordinator.finsh()
}
}
``````swift
import SwiftUIstruct NavigationActionListView: View {
typealias ViewModel = ActionListViewModel
@StateObject var viewModel: ViewModel
var body: some View {
List {
Button("Push NavigationView") {
Task { await viewModel.navigateToPushView() }
}
Button("Presents SheetView") {
Task { await viewModel.presentSheet() }
}
Button("Presents FullscreenView") {
Task { await viewModel.presentFullscreen() }
}
Button("Presents DetentsView") {
Task { await viewModel.presentDetents() }
}
Button("Presents Tabbar Coordinator") {
Task { await viewModel.presentTabbarCoordinator() }
}
}
.navigationTitle("Navigation Action List")
}
}
```_____
### 3. Create MainCoordinator and its Routes
```swift
import SUICoordinatorclass MainCoordinator: Coordinator {
override init() {
super.init()
Task {
await startFlow(route: .splash, animated: false)
}
}
override func start(animated: Bool = true) async {
let coordinator = HomeCoordinator()
await navigate(to: coordinator, presentationStyle: .fullScreenCover, animated: animated)
}
}
``````swift
import SUICoordinator
import SwiftUIenum MainRoute: RouteType {
case splash
public var presentationStyle: TransitionPresentationStyle {
switch self {
case .splash:
return .push
}
}
@ViewBuilder
public var view: any View {
switch self {
case .splash:
SplashView()
}
}
}
```
_____### Setup project
1. Create an AppDelegate class and do the following implementation or if you prefer skip this step and do the same implementation in the next step.
```swift
import SwiftUI
import SUICoordinatorclass AppDelegate: NSObject, UIApplicationDelegate {
var mainCoodinator: (any CoordinatorType)?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
mainCoodinator = MainCoordinator()
return true
}
}
```
2. In the App file, Follow next implementation:
```swift
import SwiftUI@main
struct SUICoordinatorDemoApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
if let view = appDelegate.mainCoodinator?.view {
AnyView(view)
}
}
}
}
```### Example project
For better understanding, I recommend that you take a look at the example project located in the [Example folder](https://github.com/felilo/SUICoordinator/tree/main/Examples/SUICoordinatorExample).
https://github.com/felilo/SUICoordinator/assets/10853689/90e8564e-6fa5-458b-b2a3-23d10f5aebb4
_____
### Features
These are the most important features and actions that you can perform:
#### RouteType
To create any route in `SUICoordinator` you need to extend your object to the `RouteType` protocol; Additionally, you can add your custom functions if you need. As you can see, in our example we are using custom types (`enums`) to implement it.
Last but not least, you can also use `DefaultRoute` to create custom routes as demonstrated in the `TabBarFlowCoordinator` [example](https://github.com/felilo/SUICoordinator/blob/main/Examples/SUICoordinatorExample/SUICoordinatorExample/Coordinators/TabbarFlow/TabbarFlowCoordinator.swift)
#### Router
The router is encharge to manage the navigation stack and coordinate the transitions between different views. It abstracts away the navigation details from the views, allowing them to focus on their specific features such as:
Name
Parametes
Description
navigate(_)
-
to:Route
, -
presentationStyle:TransitionPresentationStyle?
, default:nil
, -
animated:Bool?
, defaulttrue
,
Is an async function, allows you to navigate among the views that were defined in the Route. The types of presentation are Push, Sheet, Fullscreen and Detents
present(_)
-
_ view:ViewType
-
presentationStyle:TransitionPresentationStyle?
, default:nil
, -
animated:Bool?
, defaulttrue
,
Is an async function, presents a view such as Sheet, Fullscreen or Detents
pop(_)
-
animated:Bool?
, defaulttrue
,
Is an async function, pops the top view from the navigation stack and updates the display.
popToRoot(_)
-
animated:Bool?
, defaulttrue
,
Is an async function, pops all the views on the stack except the root view and updates the display.
dismiss(_)
-
animated:Bool?
, defaulttrue
,
Is an async function, dismisses the view that was presented modally by the view.
popToView(_)
-
_ view:T
-
animated:Bool?
, defaulttrue
,
Is an async function, pops views until the specified view is at the top of the navigation stack. Example:
router.popToView(MyView.self)
close(_)
-
animated:Bool?
, defaulttrue
,
Is an async function, dismiss or pops the last view presented in the Coordinator.
#### Coordinator
Acts as a separate entity from the views, decoupling the navigation logic from the presentation logic. This separation of concerns allows the views to focus solely on their specific functionalities, while the Navigation Coordinator takes charge of the app's overall navigation flow. Some features are:
Name
Parametes
Description
router
Variable of Route type which allow performs action router.
startFlow(_)
-
to:Route
-
transitionStyle:TransitionPresentationStyle?
, default:automatic
, -
animated:Bool?
, defaulttrue
Is an async function, cleans the navigation stack and runs the navigation flow.
finishFlow(_)
-
animated:Bool?
, defaulttrue
,
Is an async function, pops all the views on the stack including the root view, dismisses all the modal view and remove the current coordinator from the coordinator stack.
forcePresentation(_)
-
route:Route
-
presentationStyle:TransitionPresentationStyle?
, default:automatic
, -
animated:Bool?
, defaulttrue
, -
mainCoordinator:Coordinator?
, default:mainCoordinator
Is an async function, puts the current coordinator at the top of the coordinator stack, making it the active and visible coordinator. This feature is very useful to start the navigation flow from push notifications, notification center, atypical flows, etc.
navigate(_)
-
to:Coordinator
-
presentationStyle:TransitionPresentationStyle?
, default:automatic
, -
animated:Bool?
, defaulttrue
,
Is an async function, allows you to navigate among the Coordinators. It calls the
start()
function.
finishFlow(_)
-
animated:Bool?
, defaulttrue
,
Is an async function, pops all the views on the stack including the root view, dismisses all the modal view and remove the current coordinator from the coordinator stack.
#### TabbarCoordinator
It works the same as Coordinator but has the following additional features:
Name
Parametes
Description
currentPage
Variable of Page
type which allow set and get the tab selected
getCoordinatorSelected()
-
mainCoordinator:Coordinator?
, defaultmainCoordinator
,
Returns the coordinator selected that is associated to the selected tab
setPages(_)
-
_values:[PAGE]?
, defaultmainCoordinator
,
Is an async function, updates the page set.
getCoordinator(_)
-
position:Int
Returns the coordinator at the position given as parameter
setBadge
-
PassthroughSubject:(String?, Page)?
Variable that allows set the badge of a tab
_____
### Installation 💾
SPM
Open Xcode and your project, click File / Swift Packages / Add package dependency... . In the textfield "Enter package repository URL", write and press Next twice
_____
## Contributing
Contributions to the SUICoordinator library are welcome! To contribute, simply fork this repository and make your changes in a new branch. When your changes are ready, submit a pull request to this repository for review.