UIFlow - iOS 项目的导航和数据交互框架
TL; DR
UIFlow
是一个框架,允许 Coordinator
处理 UIViewControllers
之间的导航。您可以使用您的 ViewControllers
(或从 UIFlowViewController
类派生)并创建 Coordinators
来处理所有内容。
还有一个 Instantiable
协议,可以从同名的故事板或 xib 文件实例化您的 ViewController
。如果您真的很想了解这如何使您的生活更轻松,请完全阅读此文档,只需几分钟,但您也可以下载代码,并根据提供的 demo
项目查看其运行。
最后,您的 ViewController
不需要了解任何关于导航的内容,可能看起来像这样
import Combine
import UIFlow
class LoginViewController: UIFlowViewController {
// MARK: - Dependencies
var viewModel: LoginViewModel!
// MARK: - VC Actions
var finishedLogin: (() -> Void)?
var goToUserRegistration: (() -> Void)?
// MARK: - IB Outlets
@IBOutlet private weak var emailTextField: UITextField!
@IBOutlet private weak var passwordTextField: UITextField!
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
observe(viewModel.$state) { [weak self] state in
guard let self = self else { return }
switch state {
case .loggedIn: self.finishedLogin?()
case .loginError: self.showError()
}
}
}
// MARK: - IB Actions
@IBAction func userRegistrationButtonTouchUpInside(_ sender: Any) {
goToUserRegistration?()
}
@IBAction func loginButtonTouchUpInside(_ sender: Any) {
viewModel.login(email: emailTextField.text, password: passwordTextField.text)
}
}
谢谢!
它是什么?
有许多关于如何构建 iOS
应用的设计模式、架构和想法。嗯,这是另一个,但这是一个不错的框架,它不会让你感到震惊,这是结合了一些对我需求来说非常好用 ideas。
在多年的不同尺寸和类型的 app 工作中,我发现我可以使用一个简单的结构来处理它们中的大多数,但也应该遵循一些原则:
- 易于理解
- 易于使用
- 将模型交互与用户交互分离
- 使所有的 ViewController 都可以独立和可测试
- 使 ViewController 可在不同的流中重用,而无需在它们中添加自定义代码
- 使其具有可扩展性
是很棒的清单,对吧?遵循这些概念,我想出了 UIFlow
,我希望它可以帮助您在项目中。
你可能想知道"关于 SwiftUI 应该怎么做?" 。这是 iOS 开发的一个重要步骤,但是由于许多项目正在使用 UIKit
,因此要迁移到 SwiftUI
几乎是不可能的事情,而且旧的 iOS
版本可能无法使用,因此 UIFlow
可能是一个不错的选择。
设置
CocoaPods
如果你正在使用 CocoaPods,请将其添加到你的 Podfile 中,并运行 pod install
。
target 'Your target name' do
pod 'UIFlow', '~> 2.1'
end
手动安装
如果你想要手动将库添加到你的项目中而不使用包管理器,只需将 Classes
文件夹中的所有文件复制到你的项目中即可。
它是如何工作的?
我们先从基础开始。 UIFlow
非常简单,它仅包含这些协议/模型:
- 可实例化
- 协调器
- UIFlowViewController
真的就这么简单吗?是的!一般来说,一个简单的应用看起来大概就是这样。
可见?接下来,我们来谈谈它的每个部分。
概念
可实例化的
我见过一些协议用于从 storyboards
或 xibs
按文件名实例化 ViewControllers
,所以我将它们组合成一个独特的协议。这里您可以看到使用是多么简单,比如我们创建了这个 LoginViewController
并将其设置为初始视图控制器
- LoginViewController.storyboard
- LoginViewController.swift
这是在 Coordinator
中使用它的方法
func navigateToLogin(animated: Bool) {
guard let scene = LoginViewController.instantiate() else { return }
move(to: scene, animated: animated)
}
就这么简单!
协调器
您可能已经看到了许多不同版本的 Coordinator
实现。它与那些类似,但最大的区别是它一次只运行一个子项,而在通常情况下,您会看到很多子项。
View Controllers
不应该知道用户从哪里来以及会去哪里。这几乎类似于 依赖注入
的想法,因为 ViewController
只会做它本应做的事情,并告诉 Coordinator
用户的 意图
。
例如
在 LoginViewController
上,将有一些带有验证和登录服务的输入。登录成功后,用户应该去哪里?去菜单?去只有登录用户才能看到的特定区域?好吧,这没关系!
LoginViewController
将只会告诉 Coordinator
登录成功,并将导航留给 Coordinator
。这样一来,一个 AccessCoordinator
就可以简单地导航到菜单,而一个 PremiumCoordinator
就可以导航到秘密区域。
看发生了什么?LoginViewController
不关心它应该去哪里,只需要做它打算做的事情。
让我们看看它是如何实际工作的
import UIFlow
class AppCoordinator: Coordinator {
// MARK: - Properties
var navigation: UINavigationController
weak var startViewController: UIViewController?
weak var topViewController: UIViewController?
var parent: Coordinator?
var child: Coordinator?
var firstTime = true
var loggedIn = false
// MARK: - Initialization
init(navigation: UINavigationController) {
self.navigation = navigation
}
// MARK: - Coordinator
func start(animated: Bool) {
if firstTime {
navigateToOnboarding(animated: animated)
} else if loggedIn {
navigateToMenu(animated: animated)
} else {
navigateToLogin(animated: animated)
}
}
// MARK: - Login
func navigateToLogin(animated: Bool) {
guard let scene = LoginViewController.instantiate() else { return }
scene.goToUserRegistration = { [weak self] in
self?.navigateToUserRegistration(animated: true)
}
scene.finishedLogin = { [weak self] in
self?.loggedIn = true
self?.start(animated: true)
}
move(to: scene, animated: animated)
}
...
}
...
子协调器
子 Coordinator
是一个将执行的不同流程,当它完成后,它将返回到父流程。您可以将其视为涉及许多场景的 app 的不同功能。唯一不同的是,Coordinator
一次只执行一个子项,但一个子项可以有它自己的子项,依此类推。
为什么一次只养一个孩子,而不是同时养育多个孩子?很简单,在大多数情况下,你只会看到一个流程,完成时会返回给父流程。正因为如此,在 UIFlow
中,你一次只能运行一个子流程。
当你启动子流程时,父流程会保留最后一个正在显示的 ViewController
的引用,当子流程结束时,父流程会返回导航栈中的那个 ViewController
。
这是一个在你的项目中可以做的例子。
那么如何转到子流程呢?就像这样
func goToItems(_ sender: MenuViewController) {
let itemsCoordinator = ItemsCoordinator(navigation: navigation)
start(child: itemsCoordinator, animated: true)
}
很简单。你在流程中完成业务了吗?你可以这样返回
func closeItemsList(_ sender: ItemsListViewController) {
finish(animated: true)
}
就这样了!它会返回到父流程。
TabBarCoordinator
有时在你的应用中需要一个 TabBar
,因此为了处理这种类型的导航,我创建了 TabBarCoordinator
。正如预期的那样,它非常简单。
import UIFlow
class MenuCoordinator: TabBarCoordinator {
// MARK: - Coordinator Properties
var navigation: UINavigationController
weak var startViewController: UIViewController?
weak var topViewController: UIViewController?
var parent: Coordinator?
var child: Coordinator?
// MARK: - TabBarCoordinator Properties
let tabBar: UITabBarController
var items: [Coordinator]
// MARK: - Initialization
init(navigation: UINavigationController,
tabBar: UITabBarController = UITabBarController()) {
self.navigation = navigation
self.tabBar = tabBar
items = []
}
}
使用也非常简单,你只需要使用以下方法之一添加标签栏项
let tabBarItem = UITabBarItem(title: "Item 1",
image: nil,
selectedImage: nil)
coordinator.addItem(tabBarItem: tabBarItem,
coordinator: TestCoordinator(navigation: UINavigationController()))
coordinator.addItem(title: "Item 2",
image: nil,
selectedImage: nil,
coordinator: AnotherCoordinator())
最后,如果你需要移除一个项,只需这样做
coordinator.removeItem(at: 0)
UIFlowViewController
UIFlowViewController
是一个非常简单的 UIViewController
,它实现了使用 observe()
方法观察模型的 Combine
框架的基本功能。因此,如果你想要从中受益,可以简单地这样使用它
import UIFlow
class NewItemViewController: UIFlowViewController {
// MARK: - Dependencies
var viewModel: NewItemViewModel!
// MARK: - IB Outlets
@IBOutlet weak var itemNameTextField: UITextField!
// MARK: - VC Actions
var newItemCompleted: (() -> Void)?
var newItemCanceled: (() -> Void)?
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
observe(viewModel.$state) { [weak self] value in
guard let self = self else { return }
if value == .itemAdded {
self.itemNameTextField.text = nil
self.showAlert(title: "Nice!", message: "Item added!")
}
}
}
// MARK: - Alerts
func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
}
// MARK: - IB Outlets
@IBAction func addItemButtonTouchUpInside(_ sender: Any) {
guard let itemName = itemNameTextField.text, !itemName.isEmpty else { return }
viewModel.addItem(name: itemName)
}
@IBAction func cancelButtonTouchUpInside(_ sender: Any) {
if viewModel.state == .itemAdded {
newItemCompleted?()
} else {
newItemCanceled?()
}
}
}
看看 NewItemViewController
多么简单,以及它对数据本身和导航的关注有多少?这正是我试图实现的目标!NewItemViewController
只顾用户的交互,让 ViewModel
和 Coordinator
做它们的工作。
移除观察者
UIFlowViewController
会在 deinit
时移除所有观察者,但如果存在另一个对该视图控制器的强引用,则不会调用 deinit
,但你可以通过调用 removeObservers()
轻松移除所有观察者。
@IBAction func backButtonTapped(sender: UIButton) {
removeObservers()
backAction()
}
👍
感谢本框架的创建得以实现,得益于以下令人惊叹的人们
- Gray Company: https://www.graycompany.com.br/
- Swift by Sundell: https://www.swiftbysundell.com/
- Hacking with Swift: https://www.hackingwithswift.com/
- Ricardo Rauber: http://ricardorauber.com/
欢迎反馈
如果您注意到任何问题、遇到了困难,或者只想聊天,请自由地创建一个问题。我们将很乐意为您提供帮助。
许可证
UIFlow 采用 MIT 许可证 发布。