Build

StateViewController

在创建丰富的视图控制器时,通常会需要一个单独的视图控制器类来管理许多其他视图、控件和其他用户界面元素的外观,而这些外观都基于一个状态。 这个状态通常又源于需要同步的多个来源,以便正确表示一个单一可靠的状态。 通常,最终结果被称为巨型视图控制器问题,通常通过放弃 MVC 模式来解决,而 MVC 模式是 UIKit 中的主要设计模式。 虽然其他模式,例如 MVVMMVP,可以解决一些问题,但顺应潮流而非逆流而上,可以更方便地与 UIKit 交互。

此仓库包含一个 UIViewController 子类,可以实现视图控制器的模块化和解耦,从而大幅减小单个视图控制器的大小,而无需放弃 MVC 作为设计模式。

要求

概述

StateViewController 是一个容器视图控制器,它为您定义的任何给定状态(例如 loadinglistediting)呈现一个或多个视图控制器。 它管理每个子视图控制器的外观周期,确保子视图控制器的视图生命周期保持完整且有序,并通知您有关状态转换以及哪些子视图控制器即将出现在视图层次结构中或从视图层次结构中消失。 这使您可以组合多个视图控制器并在整个应用程序中重复使用它们。 状态视图控制器还提供对状态之间转换进行动画处理的广泛支持。

屏幕上可见哪些视图控制器由 children(for:) 决定。

外观转换期间的状态转换

在屏幕上呈现时,状态视图控制器需要一个初始状态作为起点。 在其外观转换期间,将调用 loadAppearanceState() 方法来查询适合过渡到状态,因为状态视图控制器出现在屏幕上。 如果外观转换是动画的,则状态转换动画将得到尊重,并且目标子视图控制器可以选择异步显示。 如果外观转换不是动画的,则所有子视图控制器会立即放置在屏幕上。

loadAppearanceState() 必须同步执行,并且是查询任何持久层以获取可用数据,确定最终状态是否已准备好的好地方。

During appearance cycle

屏幕上的状态转换

当在屏幕上时,调用 setNeedsTransition:to: 将触发从当前状态到目标状态的转换。 一种常见的做法是让从一种状态到另一种状态的转换触发一个异步操作(例如网络调用),该操作完成后,会根据异步操作的成功与否请求第三种状态。

Between appearance cycle

文档

源代码文档可以在这里找到。

安装

此模块可通过 Carthage 获得。 修改您的 Cartfile 以包含 StateViewController

github "davidask/StateViewController"

用法

import StateViewController

子类化 StateViewController

要使用 StateViewController,您必须覆盖它。 该类指定具有 State 子类型的泛型。 可以设计状态类型以容纳您的视图控制器所需的实际模型数据,但这是一个可选的设计决策。 例如,您可以创建一个仅确定抽象状态的状态

enum MyState {
    case loading
    case ready
    case error
}

或者,您可以定义一个状态,该状态本身包含模型数据

enum MyState {
    case loading
    case ready(MyModel)
    case error(Error)
}

拥有状态后,创建 StateViewController 的子类。

class MyStateViewController: StateViewController<MyState>

每次 StateViewController 即将出现在屏幕上时,它都会调用其 loadAppearanceState() 方法。 此方法返回一个状态,该状态应在视图控制器显示在屏幕上后立即准备好显示。 覆盖此方法以确定根据缓存数据或数据库的内容立即显示什么状态是合适的。

override func loadAppearanceState() -> MyState {
    if let myModel = MyCache.cachedModel {
        return .ready(myModel)
    } else {
        return .loading
    }
}

每个状态可以由零个或多个视图控制器表示。 要提供哪些视图控制器对什么状态可见,请覆盖 children(for:)

override func children(for state: MyState) -> [UIViewController] {
    switch state {
        case .loading:
            return [ActivityIndicatorViewController()]
        case .ready:
            return [myTableViewController]
        case .error:
            return [ErrorViewController()]
    }
}

您会收到有关状态转换何时开始以及何时完成的回调。 willTransition(to:animated:) 是为您准备即将出现的子视图控制器的绝佳位置。

override func willTransition(to nextState: MyState, animated: Bool) {
    switch nextState {
        case .ready(let model):
            navigationItem.setRightBarButton(myBarButtonItem, animated: animated)
            myTableViewController.setRows(model.objects)
        default:
            navigationItem.setRightBarButton(nil, animated: animated)
    }
}

当调用 didTransition(from:animated:) 时,状态转换已成功完成。 这是调用其他方法的好时机,这些方法反过来会触发另一个状态转换。

override func didTransition(from previousState: State?, animated: Bool) {
    switch currentState {
        case .loading:
            fetchData { model in
                self.setNeedsTransition(to: .ready(model), animated: true)
            }
        defualt:
            break
    }
}

您的 StateViewController 现在已准备就绪,并将根据状态在视图控制器之间切换。 使用 setNeedsTransition(:to:animated:) 您可以在状态视图控制器子类的生命周期内,在各种状态之间进行转换。

还有多个其他回调可用于确定子视图控制器何时出现或消失。 请参考文档或示例

提供子视图控制器之间的过渡

符合 StateViewControllerTransitioning 协议的 StateViewController 的子视图控制器可以单独控制自己的过渡。 可用的方法提供以下功能

示例

在此项目中包含的示例应用程序中,状态视图控制器在两个视图控制器之间切换。 首先,它显示动画活动指示器视图控制器的过渡,同时执行网络调用。 网络调用成功完成后,它将转换为显示具有已加载内容的表格视图的状态。

贡献

请随时贡献 StateViewController,请查看 LICENSE 文件以获取更多信息。

鸣谢

David Ask