Swift学徒

AsyncDisplayKit 教程(3):TableNode

本教程的初始工程 RainforestStarter 可以点这里获取。

运行程序,App 中包含了一个 UITableView,显示了一个野生动物的列表。滚动页面,帧率开始下降。这个 App 明显的需要进行性能上调优。

用 TableNode 替换 Table View

在 AnimalTableController.m 中引入 AsyncDisplayKit:

1
#import <AsyncDisplayKit/AsyncDisplayKit.h>

将 tableView 属性声明:

1
@property (strong, nonatomic) UITableView *tableView;

替换为 tableNode:

1
@property (strong, nonatomic) ASTableNode *tableNode;

这会导致许多代码出现错误,这些错误会引导你去完成整个转换。

我们需要将所有的 tableView 实例修改为 tableNode,其中注意这些地方:

  1. 将一个 ASTableNode 实例分配给 tableNode。
  2. ASTableNode 对象没有 registerCalss: forCellReuseIdentifier 方法。
  3. 你不能将 node 添加为一个 subview。

viewDidLoad 方法修改为:

1
2
3
4
5
6
7
8
9
10
11
- (void)viewDidLoad {
[super viewDidLoad];
self.tableNode = [[ASTableNode alloc]initWithStyle:UITableViewStylePlain];
[self.view addSubnode:self.tableNode];
[self wireDelegation];
[self applyStyle];
}

这里,我们调用它了 UIVIew 的 addSubnode: 方法,这个方法是通过 Category 的方式被添加到 UIView 上的。它实际上等于:

1
[self.view addSubview:self.tableNode.view];

修改 viewWillLayoutSubviews 方法,将 self.tableView 替换为 self.tableNode :

1
2
3
4
5
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
self.tableNode.frame = self.view.bounds;
}

修改 applyStyle 方法:

1
2
3
4
- (void)applyStyle {
self.view.backgroundColor = [UIColor blackColor];
self.tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone;
}

为了设置 tableView 的 separatorStyle 属性,我们必须访问 tableNode 的 view 属性。ASTableNode 并没有将 UITableView 的所有属性都暴露出来,因此为了访问 UITableView 的某些属性,你必须访问位于 TableNode 底下的 UITableView 对象。

设置 TableNote 的 Data Source & Delegate

和 UITableView 一样,ASTableNode 也有 Data Source 和 Delegate。TableNode 的 ASTableDataSource 和 ASTableDelegate 协议和 UITableViewDataSource 和 UITableViewDelegate 协议非常相似。

wireDelegation方法中的 tableView 修改为 tableNode

1
2
3
4
- (void)wireDelegation {
self.tableNode.dataSource = self;
self.tableNode.delegate = self;
}

并且让 controller 遵守 ASTableDataSource 和 ASTableDelegate 协议。

实现 ASTableDataSource 协议

首先,将 UITableViewDataSource 方法 tableView:numberOfRowsInSection: 修改为 ASTableDataSource 协议的版本。

1
2
3
- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section {
return self.animals.count;
}

然后,ASTableNodes 返回 cell 的方式和 UITableView 有所不同。将 tableView:cellForRowAtIndexPath:方法替换为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1
- (ASCellNodeBlock)tableNode:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath {
//2
RainforestCardInfo *animal = self.animals[indexPath.row];
//3
return ^{
//4
CardNode *cardNode = [[CardNode alloc] initWithAnimal:animal];
//You'll add something extra here later...
return cardNode;
};
}

代码解释:

  1. ASCellNode 等价于 UITableViewCell 或 UICollectionViewCell。注意这个方法返回的是一个 ASCellNodeBlock。因为一个 ASTableNode 在内部维护了所有的 cell,为每个 cell 的 Index Path 指定了一个 block,这样就能够并发的初始化所有的 cell。
  2. 得到一个数据模型对象。闭包会捕捉到这个对象。IndexPath 不需要在闭包中使用,有可能在闭包调用前数据会发生改变。
  3. 闭包中返回的类型必须是 ASCellNode。
  4. 不需要关心 cell 重用的问题,因此只需要实例化一个 cell。注意,你返回的是一个 CardNode 而不是 CardCell。

实现 ASTableDelegate 协议

在使用 UITableView 的时候通常都需要实现 tableView:heightForRowAtIndexPath: 方法来计算每个 cell 的高度。

ASTableDelegate 中没有 tableView:heightForRowAtIndexPath: 方法。如果使用 ASDK, 所有的 ASCellNodes 都自己负责计算它们的大小。不需要提供一个固定的高度,你可以为每个 cell 指定一个最大尺寸和最小尺寸。这个例子中,你需要让每个 cell 至少占据屏幕 2/3 的高度。

将 -tableView:heightForRowAtIndexPath: 方法替换为:

1
2
3
4
5
6
7
8
-(ASSizeRange)tableNode:(ASTableNode *)tableNode constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat width = [UIScreen mainScreen].bounds.size.width;
CGSize min = CGSizeMake(width, ([UIScreen mainScreen].bounds.size.height/3) * 2);
CGSize max = CGSizeMake(width, INFINITY);
return ASSizeRangeMake(min, max);
}

搞定! 运行一下,很流畅~


万恶胖为首 wechat
关注公众号(ID:SwiftBetter),进一步探讨交流。