Swift for UIKit 中协调器模板的简单、强大和优雅的实现
安装
Swift Package Manager
https://github.com/bartleby/Jumper.git
如何使用跳线
协调 员
开箱即用的跳线具有三种协调器协议,您的协调器可以实现这些协议:
你也可以实现自己的协调器,参见源代码。
RootCoordinable
– 它是一个容器,非常适合在应用程序中启动逻辑。UIViewController
NavigationCoordinable
– 用于堆栈导航TabCoordinable
- 实现“UITabBarController”
根可协调
final class AppCoordinator: RootCoordinable {
// step 1
var navigation: RootNavigation<AppCoordinator> = .init(initial: \.onboarding)
// step 2
@Route var onboarding = onboardingScreen
@Route var home = homeScreen
// step 3
func onboardingScreen() -> ScreenView {
OnboardingScreenView()
}
func homeScreen() -> TabBarCoordinator {
TabBarCoordinator()
}
}
In this example, an is created that implements the protocolAppCoordinator
RootCoordinable
- Step 1 – You have to implement the property and initialize it with by default
navigation
Route
- Step 2 – Initialization of transitions, set using the keyword which should indicate the function of creating a or another for the transition
@Route
View
Coordinator
- Step 3 – Implementation of methods referenced by
Route
List of transition methods
root(\.someRoute)
Replaces the current view or coordinatorroot(\.someRoute, input: "any type")
isRoot(\.someRoute)
Returns a boolean value that indicates whether the given is rootRoute
hasRoot(\.someRoute)
Returns the root coordinator or nil if the specified is not rootRoute
present(\.someRoute)
Is presented by a view or coordinatorpresent(\.someRoute, input: "any type")
present(\.someRoute, input: "any type", animated: false)
dismiss()
Dismiss the current Coordinator
You can pass an argument to each transition method using the field, which will be passed to the view/coordinator creation function.input
@Route var userList = userListScreen
func userListScreen(listData: [User]) -> UserListCoordinator {
UserListCoordinator(data: listData)
}
calling such a transition will look like this
coordinator.present(\.userList, input: userListData)
NavigationCoordinable
final class AuthorizationCoordinator: NavigationCoordinable {
var navigation: Navigation<AuthorizationCoordinator> = .init(initial: \.authorization)
@Route var authorization = authorizationScreen
@Route var registration = registrationScreen
func authorizationScreen() -> ScreenView {
AuthorizationScreen()
}
func registrationScreen() -> ScreenView {
RegistrationScreen()
}
}
List of transition methods
push(\.someRoute)
push(\.someRoute, input: "any type")
push(\.someRoute, animated: false)
push(stack: )
pop(\.someRoute)
pop(\.someRoute, animated: false)
pop(to: \.someRoute)
going to the specifiedRoute
pop(to: \.someRoute, animated: false)
popToRoot()
popToRoot(animated: false)
present(\.someRoute)
present(\.someRoute, input: "any type")
present(\.someRoute, input: "any type", animated: false)
dismiss()
Dismiss the current Coordinator
using the method you can several into the navigation stack at once, and the animation will be applied only for the last transitionpush(stack:)
push
Routes
coordinator.push {
\SettingsCoordinator.yellow
\SettingsCoordinator.green
\SettingsCoordinator.green
\SettingsCoordinator.green
\SettingsCoordinator.yellow
\SettingsCoordinator.green
}
you can do the same with the chainRoute
coordinator
.push(\.yellow, animated: false)
.push(\.green, animated: false)
.push(\.green, animated: false)
.push(\.green, animated: false)
.push(\.yellow, animated: false)
.push(\.green, animated: true)
TabCoordinable
final class TabBarCoordinator: TabCoordinable {
// Step 1
var navigation: TabNavigation<TabBarCoordinator> = .init {
\TabBarCoordinator.main
\TabBarCoordinator.settings
}
// Step 2
@Route(tabItem: mainTab) var main = mainScreen
@Route(tabItem: settingsTab) var settings = settingsScreen
// Step 3
func mainScreen() -> MainCoordinator {
MainCoordinator()
}
func settingsScreen() -> SettingsCoordinator {
SettingsCoordinator()
}
// Step 4
func mainTab() -> UITabBarItem {
UITabBarItem()
.image(UIImage(systemName: "circle.fill"))
.title("Main")
}
func settingsTab() -> UITabBarItem {
UITabBarItem()
.image(UIImage(systemName: "square.fill"))
.title("Settings")
}
}
Here everything is similar to other coordinators, with a small exception, a new argument appears in @Route, to which you must pass TabItem, and in the initialization of the property, a list of routes that will be tabs is now passednavigation
- Step 1 – Initialize with several that will be tabs in the tabbar
navigation
Routes
- Step 2 – Define by specifying TabItem in the argument
Route
- Step 3 – Define the coordinators for transitions
- Step 4 – Define the methods that will return TabItem’s
List of transition methods
focus(\.someTabRoute)
switching to tabpresent(\.someRoute)
presentation ofRoute
dismiss()
Dismiss the current Coordinator
Alert’s
In order to show the alert and other pop-up elements through the coordinator, you must support the protocolScreenViewPresentable
For example, create the protocol and implement the Alert display logic in it.
To get the controller to which you want to show the popup element, call the methodAlertCoordinable
view()
let controller = view()
public protocol AlertCoordinable: ScreenViewPresentable {
func showAlert(title: String, message: String)
}
extension AlertCoordinable {
func presentAlert(title: String, message: String) {
let controller = view()
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let completeAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(completeAction)
controller.present(alertController, animated: true, completion: nil)
}
}
Implement the ‘AlertCoordinable` protocol in the coordinator. Now the coordinator has the opportunity to display an alert
final class SettingsCoordinator: NavigationCoordinable, AlertCoordinable {
//...
}
coordinator.presentAlert(title: "Title", message: "Message")
Customizing
Sometimes you may need to access the , or controller from your coordinator, there is a method for thisUITabBarController
UINavigationController
UIViewController
configure(controller: )
final class MainCoordinator: NavigationCoordinable {
//...
func configure(controller: UINavigationController) {
// Customize here
}
}
Chaining
One of the strengths of is the integration of transitions into chainsJumper
coordinator
.hasRoot(\.tabBar)
.focus(\.todoList)
.push(\.todoDetail, input: todoIdentifier)
.present(\.todoEditor)
each transition, if it is a transition to the coordinator, returns the transition coordinator, if it is a transition to the view, then the current coordinator is returned.
For example:
There are two transitions in the coordinator:SettingsCoordinator
final class SettingsCoordinator: NavigationCoordinable {
//...
@Route var rateApp = rateAppScreen
@Route var notification = notificationScreen
func rateAppScreen() -> ScreenView {
RateAppViewController()
}
func notificationScreen() -> NotificationCoordinator {
NotificationCoordinator()
}
}
coordinator.present(\.rateApp) \\ return SettingsCoordinator
coordinator.present(\.notification) \\ return NotificationCoordinator
Deep Linking
By combining into chains, you get out of the box, to implement them, define the method in Route
DeepLink
scene(scene:, openURLContexts:)
SceneDelegate
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
appCoordinator.onOpenURL(URLContexts.first?.url)
}
define the method in your app coordinatoronOpenURL(url:)
func onOpenURL(_ url: URL?) {
guard let url = url else { return }
guard let deepLink = try? DeepLink(url: url) else { return }
if let coordinator = self.hasRoot(\.home) {
switch deepLink {
case .todo(let id):
coordinator
.focus(\.main)
.present(\.todo, input: id)
case .settings:
coordinator.focus(\.settings)
case .home:
coordinator.focus(\.main)
}
}
}
You can see the implementation of in the Demo project.DeepLink
To test the ‘DeepLink ‘ use the terminal command xcrun simctl openurl booted <url>
deep links that are configured in the Demo project
Switching to Main Tab
xcrun simctl openurl booted jumper://io.idevs/home
Switching to Settings Tab
xcrun simctl openurl booted jumper://io.idevs/settings
Opens the modal view and passes the argument to it
hello-world
xcrun simctl openurl booted jumper://io.idevs/todo/hello-world
Demo project
Download the demo project from repository
License
MIT license. See the LICENSE file for details.