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

来源:互联网 发布:小米note查看网络制式 编辑:程序博客网 时间:2024/06/04 23:18

原理

添加一个自定义的 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! 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 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 = 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 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 = 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


0 0