原理
添加一个自定义的 View 到 ASCellNode 中并不是一件容易的事情,和添加一个原生 ASDisplayNode 不一样的是,你需要自行处理 Cell 被重新渲染时的状态。
我们先描述一下 ASCellNode 的生命周期
一个 ASDisplayNode 在 init 至 layout 的过程中,都只是属性的操作,这个操作并不会生成任何实际的 UIView,这也是为什么 AsyncDisplayKit 高效的原因之一,将 UIView 实例化的过程推迟至最终使用时。
一个 ASDisplayNode 中的 UIView 生成、清除的时机由 ASTableView决定, ASTableView 总是保证只保留用户所看到的 Node 存在 UIView 实例。
因此, ASCellNode 中的 subnode 也同时遵循上图的生命周期。
示例
在 AsyncDisplayKit 系列教程 —— ASTableView 一文中,已经演示过如何添加一个 ASImageNode 和 ASTextNode。
现在,我们演示一下如何添加一个 UIActivityIndicatorView 到 Cell 中, UIActivityIndicatorView 并没有对应的 ASDisplayNode 子类实现。因此,我们需要创建一个 ASDisplayNode ,使用block方法返回 UIActivityIndicatorView。
let activityIndicator = ASDisplayNode { () -> UIView! in
let view = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
view.backgroundColor = UIColor.clearColor()
view.hidesWhenStopped = true
return view
}
之后,就可以像普通的 ASDisplayNode 一样将其添加至 subnode 中。
override init!() {
super.init()
addSubnode(activityIndicator)
}
一套完整的代码如下图所示
//
// ViewController.swift
// AsyncDisplayKit-Issue-4
//
// Created by 崔 明辉 on 15/11/28.
// Copyright © 2015年 Pony.Cui. All rights reserved.
//
import UIKit
class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelegate {
let tableView = ASTableView()
deinit {
tableView.asyncDelegate = nil // 记得在这里将 delegate 设为 nil,否则有可能崩溃
tableView.asyncDataSource = nil // dataSource 也是一样
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.asyncDataSource = self
tableView.asyncDelegate = self
self.view.addSubview(tableView)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.frame = view.bounds
}
func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
return 1
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(tableView: ASTableView!, nodeForRowAtIndexPath indexPath: NSIndexPath!) -> ASCellNode! {
let cellNode = CustomCellNode()
cellNode.startAnimating()
return cellNode
}
}
class CustomCellNode: ASCellNode {
let activityIndicator = ASDisplayNode { () -> UIView! in
let view = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
view.backgroundColor = UIColor.clearColor()
view.hidesWhenStopped = true
return view
}
override init!() {
super.init()
addSubnode(activityIndicator)
}
func startAnimating() {
if let activityIndicatorView = activityIndicator.view as? UIActivityIndicatorView {
activityIndicatorView.startAnimating()
}
}
override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize {
return CGSize(width: constrainedSize.width, height: 44)
}
override func layout() {
activityIndicator.frame = CGRect(x: self.calculatedSize.width / 2.0 - 22.0, y: 11, width: 44, height: 44)
}
}
我们指定生成 100 个 Cell,每个 Cell height = 44,运行这个Demo你可以看到菊花已经显示在界面上了。
坑
等等,看上去好像没什么问题,滑到最下面再滑回去,你会发现,什么?菊花没了!
为什么会这样!
还记得我们说过的,一个 Node 中的 UIView 是会被清除、重新生成的吗? 在滑动到下方的时候, Node 中的所有 UIView 都会被干掉,然后滑回来的时候,会被重新执行 Block 中的代码,然后重新添加到界面上。
这个时候,我们的 UIActivityIndicatorView 还没被执行 startAnimating() 方法。
要解决这个坑,也不是很难,只要在 Node 重新出现的时候,执行一下 startAnimating() 就可以了。
//
// ViewController.swift
// AsyncDisplayKit-Issue-4
//
// Created by 崔 明辉 on 15/11/28.
// Copyright © 2015年 Pony.Cui. All rights reserved.
//
import UIKit
class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelegate {
let tableView = ASTableView()
deinit {
tableView.asyncDelegate = nil // 记得在这里将 delegate 设为 nil,否则有可能崩溃
tableView.asyncDataSource = nil // dataSource 也是一样
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.asyncDataSource = self
tableView.asyncDelegate = self
self.view.addSubview(tableView)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.frame = view.bounds
}
func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
return 1
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(tableView: ASTableView!, nodeForRowAtIndexPath indexPath: NSIndexPath!) -> ASCellNode! {
let cellNode = CustomCellNode()
cellNode.startAnimating()
return cellNode
}
// 实现这个 delegate
func tableView(tableView: ASTableView!, willDisplayNodeForRowAtIndexPath indexPath: NSIndexPath!) {
if let cellNode = tableView.nodeForRowAtIndexPath(indexPath) as? CustomCellNode {
cellNode.resume()
}
}
}
class CustomCellNode: ASCellNode {
let activityIndicator = ASDisplayNode { () -> UIView! in
let view = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
view.backgroundColor = UIColor.clearColor()
view.hidesWhenStopped = true
return view
}
override init!() {
super.init()
addSubnode(activityIndicator)
}
// 使用 resume 方法调用 startAnimating
func resume() {
startAnimating()
}
func startAnimating() {
if let activityIndicatorView = activityIndicator.view as? UIActivityIndicatorView {
activityIndicatorView.startAnimating()
}
}
override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize {
return CGSize(width: constrainedSize.width, height: 44)
}
override func layout() {
activityIndicator.frame = CGRect(x: self.calculatedSize.width / 2.0 - 22.0, y: 11, width: 44, height: 44)
}
}
扩展
使用同样的方法,可以添加任意类型 UIView 到 CellNode 中,这样就不需要被 AsyncDisplayKit 束缚我们的应用了。
相关的代码可以在这个链接中找到 https://github.com/PonyCui/AsyncDisplayKit-Issue-4