常规思路
通常我们的类会在加载前开启动画,回调到来再停止:
final class EpisodeDetailViewController: UIViewController {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
let titleLabel = UILabel()
convenience init(resource: Resource<Episode>) {
self.init()
// 开始网络请求 Loading 开始加载动画
spinner.startAnimating()
sharedWebservice.load(resource) { [weak self] result in
// 回调数据到来 结束 Loading 加载动画
self?.spinner.stopAnimating()
guard let value = result.value else { return }
self?.titleLabel.text = value.title
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(spinner)
spinner.center(inView: view)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleLabel)
titleLabel.constrainEdges(toMarginOf: view)
}
}
Protocol
轻量级,而且 Swift 里有 extension,这一点比oc实现简单很多。
主要在定义时候注意
- 相同的可重用的放到extension里面
- 需要每个具体类做特殊处理的就声明在协议里,当然也可以有默认实现。
我们需要规范Resource
资源,加载视图 spinner
以及 load
方法,这里加载视图默认是 UIActivityIndicatorView
,协议定义如下:
protocol Loading {
associatedtype ResourceType
var spinner: UIActivityIndicatorView { get }
func load(resource: Resource<ResourceType>)
}
extension Loading where Self: UIViewController {
func load(resource: Resource<ResourceType>) {
spinner.startAnimating()
sharedWebservice.load(resource) { [weak self] result in
// 这里是回调处理 到这里的时候 result 就是解析好的数据格式
self?.spinner.stopAnimating()
guard let value = result.value else { return }
// TODO 这里和具体业务相关
}
}
}
ps: 加载视图可以不使用
UIActivityIndicatorView
,只需要小小修改下协议就能使用自定义。
现在 TODO
和具体业务相关,我们必须想办法规范一下,为此在协议中增加一个configure
方法 用于处理业务逻辑。
protocol Loading {
// ...
func configure(value: ResourceType)
}
extension Loading where Self: UIViewController {
func load(resource: Resource<ResourceType>) {
spinner.startAnimating()
sharedWebservice.load(resource) { [weak self] result in
self?.spinner.stopAnimating()
guard let value = result.value else { return } // TODO loading error
self?.configure(value)
}
}
}
现在用协议方法来实现:
final class EpisodeDetailViewController: UIViewController, Loading {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
let titleLabel = UILabel()
convenience init(episode: Episode) {
self.init()
configure(episode)
}
convenience init(resource: Resource<Episode>) {
self.init()
load(resource)
}
func configure(value: Episode) {
titleLabel.text = value.title
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(spinner)
spinner.center(inView: view)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(titleLabel)
titleLabel.constrainEdges(toMarginOf: view)
}
}
至于 associatedtype ResourceType
中的 ResourceType
会在 load(resource)
推断出来。
Container 容器封装
有pros也有contra,首先不利的话,是每次都要addchildViewController 不是很好,在一些情况下可能出错,比如navigation栈下貌似会出错。
我们要实现的 LoadingViewController
容器类职责:1. 数据请求load()
,具体由外部实现; 2. 加载视图的显示和隐藏。
核心代码是两个闭包:load
和 build
:
final class LoadingViewController: UIViewController {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
init<A>(load: ((Result<A>) -> ()) -> (), build: (A) -> UIViewController) {
super.init(nibName: nil, bundle: nil)
spinner.startAnimating()
load() { [weak self] result in
self?.spinner.stopAnimating()
guard let value = result.value else { return } // TODO loading error
let viewController = build(value)
self?.add(content: viewController)
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .whiteColor()
spinner.hidesWhenStopped = true
spinner.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(spinner)
spinner.center(inView: view)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func add(content content: UIViewController) {
addChildViewController(content)
view.addSubview(content.view)
content.view.translatesAutoresizingMaskIntoConstraints = false
content.view.constrainEdges(toMarginOf: view)
content.didMoveToParentViewController(self)
}
}
这是我们Loading视图控制器,注意 build
闭包会EpisodeDetailViewController
实例,具体处理业务。
EpisodeDetailViewController
和上面定义的一样:
final class EpisodeDetailViewController: UIViewController {
let titleLabel = UILabel()
convenience init(episode: Episode) {
self.init()
titleLabel.text = episode.title
}
override func viewDidLoad() {
view.backgroundColor = .whiteColor()
view.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.constrainEdges(toMarginOf: view)
}
}
现在的关键是如何把两者关联组合起来:
let episodesVC = LoadingViewController(load: { callback in
sharedWebservice.load(episodeResource, completion: callback)
}, build: EpisodeDetailViewController.init)
category
主要是今天看到高峰的文章 认为是一个不错的选择
链接:iOS weak 关键字漫谈
欢迎关注我的微博:@Ninth_Day