恢复
恢复是一个用于使用视图控制器层级创建应用的工具箱,有助于消解一个UIViewController
必须在应用中对应一个单屏的神话。使用容器和子视图控制器编写干净且易于理解的代码逻辑,保持每个组件的大小,使其更容易维护。
UIKit
自带一些内置的容器视图控制器,旨在协调应用逻辑(例如UINavigationController
、UITabBarController
、UIPageViewController
等)。它们持有其中一个或多个子视图控制器,并处理在它们之间切换所需的应用逻辑。恢复旨在使您自己的UIViewControllers
中轻松使用此模式变得更容易,帮助将应用分解为可组合的部分。您从这种模式中获得了什么?
视图控制器组合的优势
- 您的视图控制器相互依存度降低。您的视图控制器是否做些像
self.navigationController?.pushViewController(...
这样的操作?这是常见的,但现在这个视图控制器不知道如何在外部操作特定的导航堆栈。 - 这种模式可以扩展到单个屏幕。如果您有一个非常复杂的UI,而不是一个单独的、庞大的视图控制器,您可以考虑使用单个容器视图控制器来管理一些子视图控制器。
- 更容易识别错误,更有信心回归不会发生。回到上面的例子,假设您在非常复杂的UI中的一个小部件里有一个错误。作为一个单独的视图控制器,您可能不知道您的更改是否会影响到其他地方的视图控制器,甚至可能更难定位错误。但是,使用容器和子视图控制器,您可以隔离特定的组件并确保其他组件没有参与。
- 与拆分成多个
UIView
的屏幕不同,每个子UIViewController
都能享受到UIViewController
生命周期事件的好处。你可以在viewWillAppear
中提供特定的逻辑,只在屏幕的一个组件上执行。或者,当单个组件旋转到横屏模式时,你可以自定义其布局代码。
如何入门?
安装
Reinstate 通过 CocoaPods 提供。要安装它,只需将以下行添加到 Podfile 中
pod 'Reinstate'
添加和移除子视图控制器
添加 UIView
子类到 UIViewController
可能成了你的第二天性。但是,如何添加一个子 UIViewController
呢?不使用 Reinstate 的话看起来是这样
// childController is the new child view controller
self.addChildViewController(childController)
view.addSubview(childController.view)
NSLayoutConstraint.activate([
childController.view.topAnchor.constraint(equalTo: view.topAnchor),
childController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
childController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
childController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
childController.didMove(toParentViewController: self)
如果想要动画化这种变化,事情会变得更加复杂。然而,使用 Reinstate
self.addChild(childController)
// or, an animated version:
self.addChild(childController, animations: (duration: 0.3, options: .transitionCrossDissolve))
// or, add it to a subview instead of self.view:
self.addChild(childController, constrainedTo: containerView, animations: (duration: 0.3, options: .transitionCrossDissolve))
你可以以类似的方式添加和替换子视图控制器。但是,Reinstate 不仅限于添加和移除这些子视图控制器:它还提供了几个干净的可视控制器,你可以用它来管理你的应用程序的状态。
StatefulViewController
StatefulViewController
是一个子类化的 UIViewController
,它可以根据一个 State
来管理其内容。它是通过交换你可以映射到控制器状态的子视图控制器来实现的。StatefulViewController
可以大大减少混淆性代码,并为你提供了一个简单、易于阅读的 API 来指定过渡动画和设置。
import Reinstate
enum RootViewState {
case splash
case onboarding
case signIn
case home
}
class RootViewController: StatefulViewController<RootViewState> {
var currentChild: UIViewController?
override func childViewController(for state: RootState) -> UIViewController {
switch state {
case .splash:
let vc = SplashViewController()
vc.delegate = self
return vc
case .onboarding:
let vc = OnboardingViewController()
vc.delegate = self
return vc
case .signIn:
let vc = SignInViewController()
vc.delegate = self
return vc
case .home:
let vc = HomeViewController()
vc.delegate = self
return vc
}
}
override func transitionAnimation(from oldState: RootState, to newState: RootState) -> StateTransitionAnimation? {
switch (oldState, newState) {
case (.splash, _):
return .appearAndSimultaneouslyRemove(
onAppear: (0.3, .transitionCrossDissolve),
onRemove: (0.3, .transitionCrossDissolve))
case (.onboarding, .signIn), (.signIn, .home):
return .appearOverPrevious(
onAppear: (0.3, .transitionFlipFromLeft))
case (.signIn, .onboarding), (.home, .signIn):
return .appearUnderPrevious(
onRemove: (0.3, .transitionFlipFromRight))
default:
return nil
}
}
}
extension RootViewController: SplashViewControllerDelegate {
func splashViewControllerDidComplete(_ controller: SplashViewController) {
switch (UserDefaults.standard.isAuthenticated, UserDefaults.standard.hasCompletedOnboarding) {
case (true, _):
transition(to: .home, animated: true)
case (false, true):
transition(to: .signIn, animated: true)
case (false, false):
transition(to: .onboarding, animated: true)
}
}
}
extension RootViewController: OnboardingViewControllerDelegate {
func onboardingViewControllerDidComplete(_ controller: OnboardingViewController) {
UserDefaults.standard.hasCompletedOnboarding = true
transition(to: .signIn, animated: true)
}
}
// Other delegate implementations omitted for brevity
StatefulNavigationController
StatefulNavigationController
是一个围绕 UINavigationController
的包装器,它可以根据其 NavigationState
来管理其导航堆栈。与 StatefulViewController
类似,你告诉它为哪个状态创建哪个视图控制器,并告诉它在何时过渡。API 的目的是与 StatefulViewController
相似。请参见示例项目以了解用法。
StatefulTabBarController
StatefulNavigationController
是 UITabBarController
的包装器,与其他状态控制器类似。其 API 高度类似于其他两个(一个区别:Tab Bar 需要在初始化时知道所有可能的状态)。请参考示例项目了解使用方法。
示例
要运行示例项目,克隆仓库,打开并运行 Example/Reinstate.xcworkspace
。
作者
Connor Neville, [email protected]
许可
Reinstate 可在 MIT 许可下使用。有关更多信息,请参阅 LICENSE 文件。