测试已测试 | ✓ |
Lang语言 | SwiftSwift |
许可证 | MIT |
发布最后发布 | 2017年3月 |
SwiftSwift版本 | 3.0 |
SPM支持SPM | ✗ |
由 Filip Zawada 维护。
使用 FlowKit 轻松定义屏幕流程。优雅的语法,明确分离关注点以及可测试性,使其成为您当前 MV* 设置的完美插件。
let tutorialScreen = Flow(with: TutorialViewController()) { vc, lets in
vc.onContinue = lets.push(loginScreen)
}
let loginScreen = Flow(with: LoginViewController()) { vc, lets in
vc.onLogin = lets.push(dashboardScreen)
vc.onBack = lets.pop()
}
let dashboardScreen = Flow(with: DashboardViewController()) { vc, lets in
vc.onBack = lets.pop()
vc.onLogOut = lets.popToRoot()
}
这支持以下流程
____________ _________ _____________
| | | | | |
| TutorialVC | onContinue() | LoginVC | onLogin() | DashboardVC |
| | -----------> | | --------> | |
|____________| |_________| |_____________|
以下是我们的任何视图控制器可能看起来像什么
class DashboardViewController: UIViewController {
var onBack: () -> Void = {}
var onLogOut: () -> Void = {}
@IBAction func backButtonTapped(button: UIButton) {
onBack()
}
@IBAction func logOutButtonTapped(button: UIButton) {
onLogOut()
}
}
Flow
是任何 UIViewController
的包装器。通过 Flow
,您可以定义您的 ViewController
如何与其他视图控制器交互。可能的主要交互包括
push(otherViewController)
present(otherViewController)
pop()
dismiss()
这种方法有几个主要优点,因为您的 ViewController
ViewController
的入口点和出口点被明确定义 #清晰的API有多种初始化流程的方法
不带交互
let yourScreen = Flow(with: YourViewController())
// or e.g. with a custom xib
let yourScreen = Flow(with: YourViewController(nibName: "YourView", bundle: nil))
注意,由于 @autoclosure
,YourViewController()
是懒加载的,即仅在需要时才会实例化。#swiftmagic
带交互(简短版本)
let yourScreen = Flow(with: YourViewController()) { vc, lets in
vc.onBack = lets.pop()
vc.onAbout = lets.present(otherScreen)
}
带交互(详细版本)
let yourScreen = Flow<YourViewController> { lets in
// let's initialize our ViewController from a storyboard
let storyboard = UIStoryboard(name: "YourView", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "YourView") as! YourViewController
vc.onBack = lets.pop()
vc.onAbout = lets.present(otherScreen)
return vc
}
所以,让我们假设我们有一个购物应用。"ItemViewController" 展示我们的 GreatProduct™。如果用户决定购买它,"CheckoutViewController" 就会被带到屏幕上,引导用户通过结账过程。那么,"CheckoutViewController" 如何知道实际正在购买哪个产品呢?显然,它应该从 "ItemViewController" 接收这些信息。这就是如何做到这一点
class ItemViewController: UIViewController {
var item: Item? // = GreatProduct™
var quantity = 0
var onCheckout: (Item, Int) -> Void?
@IBAction func checkout(button: UIButton) {
if let item = item, onCheckout = onCheckout {
onCheckout(item, quantity)
}
}
}
class CheckoutViewController: UIViewController {
func prepare(item: Item, quantity: Int) {
print("User wants to purchase \(item) × \(quantity)")
}
}
// our flow
let checkoutScreen = Flow(with: CheckoutViewController(nibName: "CheckoutView", bundle: nil))
let itemScreen = Flow(with: ItemViewController()) { vc, lets in
vc.onCheckout = lets.push(checkoutScreen) { $0.prepare }
}
这里的技巧是将从 "onCheckout()" 传递的参数带到 "prepare()" 函数中。正是在这里 vc.onCheckout = lets.push(checkoutScreen) { $0.prepare }
实现的。简单来说,我们会这样说法
vc. onCheckout= lets.push( checkoutScreen){$0.prepare }
hey itemViewController, on checkout lets push checkout screen and prepare it
在此需要注意的是,必须确保onCheckout
方法的签名与prepare
方法的签名完全一致,这样才能成功传递参数。
在常见场景中,您可以将流程分组到单独的类中,例如可以拥有LoginFlow
(登录流程)、SignUpFlow
(注册流程)、CheckoutFlow
(结账流程)等。如果您的应用规模较小,可能只需要一个MainFlow
(主流程)就足够了。
class MainFlow {
lazy var tutorialScreen: Flow<TutorialViewController> = Flow { [unowned self] lets in
let screen = TutorialViewController()
screen.onContinue = lets.push(self.loginScreen) { $0.prepare }
return screen
}
lazy var dashboardScreen: Flow<DashboardViewController> = Flow { [unowned self] lets in
let screen = DashboardViewController()
screen.onBack = lets.pop()
screen.onLogOut = lets.popTo(self.loginScreen)
screen.onExit = lets.popToRoot()
return screen
}
lazy var loginScreen: Flow<LoginViewController> = Flow { [unowned self] lets in
let screen = LoginViewController()
screen.onLogin = lets.push(self.dashboardScreen)
screen.onBack = lets.pop()
return screen
}
}
备注1。我们不得不使用lazy var
,以便允许dashboardScreen
引用loginScreen
,反之亦然。使用常规的let
变量,编译器不会允许我们在dashboardScreen
中使用loginScreen
。
备注2。遗憾的是,由于编译器中的错误,我们必须声明变量类型,否则我们无法使用self。
。
建设中
我想创建自定义的nimble匹配器,以便使测试我们的流程变得与编写它们一样简单。
let mainScreen = Flow(with: MainViewController())
mainScreen.letsFactory = LetsSpyFactory()
let spy = mainScreen.letsFactory.makeSpy()
let vc = mainScreen.viewController
expect(vc.onBack).to(haveBeenBackedWith(spy.pop))
expect(vc.onLogOut).to(haveBeenBackedWith(spy.popTo(loginScreen)))
expect(vc.onExit).to(haveBeenBackedWith(spy.popToRoot()))
建设中
FlowKit将很好地与以下内容集成:
RxSwift(正在准备中)
let tutorialScreen = Flow(with: TutorialViewController()) { vc, lets in
vc.onContinue
.asDriver()
.drive(lets.push(loginScreen))
.addToDisposeBag(disposeBag)
}
这只是个示意图,我还不确定这将会是怎样的。
此项目由Filip Zawada创建和维护。它的创建是为了解决我在之前的几个应用中遇到的导航问题。
这个项目是针对Krzysztof Zabłocki所提出的Flow Controllers理念的下一迭代版本,他在这里进行了描述。
即将选择。
设计于波兰,用Swift构建