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

原理

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

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

AsyncDisplayKit 系列教程 —— 添加一个 UIActivityIndicatorView 到 ASCellNode_第1张图片
屏幕快照 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 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

你可能感兴趣的:(AsyncDisplayKit 系列教程 —— 添加一个 UIActivityIndicatorView 到 ASCellNode)