当前位置: 代码迷 >> 综合 >> AsyncDisplayKit 系列教程 —— 添加一个 UIActivityIndicatorView 到 ASCellNode
  详细解决方案

AsyncDisplayKit 系列教程 —— 添加一个 UIActivityIndicatorView 到 ASCellNode

热度:61   发布时间:2023-12-08 22:58:49.0

原理

添加一个自定义的 View 到 ASCellNode 中并不是一件容易的事情,和添加一个原生 ASDisplayNode 不一样的是,你需要自行处理 Cell 被重新渲染时的状态。

我们先描述一下 ASCellNode 的生命周期


屏幕快照 2015-11-28 下午3.47.04.png

一个 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! inlet view = UIActivityIndicatorView(activityIndicatorStyle: .Gray)view.backgroundColor = UIColor.clearColor()view.hidesWhenStopped = truereturn 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 UIKitclass ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelegate {let tableView = ASTableView()deinit {tableView.asyncDelegate = nil // 记得在这里将 delegate 设为 nil,否则有可能崩溃tableView.asyncDataSource = nil // dataSource 也是一样}override func viewDidLoad() {super.viewDidLoad()tableView.asyncDataSource = selftableView.asyncDelegate = selfself.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! inlet view = UIActivityIndicatorView(activityIndicatorStyle: .Gray)view.backgroundColor = UIColor.clearColor()view.hidesWhenStopped = truereturn 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 UIKitclass ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelegate {let tableView = ASTableView()deinit {tableView.asyncDelegate = nil // 记得在这里将 delegate 设为 nil,否则有可能崩溃tableView.asyncDataSource = nil // dataSource 也是一样}override func viewDidLoad() {super.viewDidLoad()tableView.asyncDataSource = selftableView.asyncDelegate = selfself.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}// 实现这个 delegatefunc tableView(tableView: ASTableView!, willDisplayNodeForRowAtIndexPath indexPath: NSIndexPath!) {if let cellNode = tableView.nodeForRowAtIndexPath(indexPath) as? CustomCellNode {cellNode.resume() }}}class CustomCellNode: ASCellNode {let activityIndicator = ASDisplayNode { () -> UIView! inlet view = UIActivityIndicatorView(activityIndicatorStyle: .Gray)view.backgroundColor = UIColor.clearColor()view.hidesWhenStopped = truereturn view}override init!() {super.init()addSubnode(activityIndicator)}// 使用 resume 方法调用 startAnimatingfunc 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