标签:添加 相对 code stack asi targe section 结束 tac
AsyncDisplayKit 官方只支持 CocoaPods 和 Carthage 安装。Carthage 不用说了,它是 Swift 包管理器。以国内的网络状况,CocoaPods 也基本不用想了,于是手动安装是我们的唯一选择。
如果你想用 git submodule 的方式安装,请参考这里。 本文使用源代码方式安装,于是你需要从 github 以 zip 包方式下载 AyncDisplayKit。下载后解压缩出 AsyncDisplay-master 目录(你可以把这个目录拷贝到要集成的项目目录下)。将这个目录拖进你的项目的项目导航窗口。
在项目导航窗口,选中你的项目的 target,在 General/Embedded Binaries 下面,点击 + 按钮,将 AsyncDisplayKit.framerork 添加到列表中。
编译项目,看看是否有报错的地方。
进入要安装的项目根目录,将 ASDK 作为一个 git submodule 添加到项目中:
git submodule add https://github.com/facebook/AsyncDisplayKit.git
要在你的 Object C 项目中使用 AsyncDisplayKit ,需要导入头文件:
#import <AsyncDisplayKit/AsyncDisplayKit.h>
在你的 View Controller 中,将用到 UITableView 的地方全部注释,我们将使用 ASTableNode 替换它以获得顺滑的滚动加载体验。因此将原来的 tableView 对象、TableView 的 Delegate 和 DataSource 协议方法和调用代码全部删除。
在 View Controller 中,定义一个属性:
@property(strong,nonatomic)ASTableNode* tableNode;
在 viewDidLoad 方法中,用下列代码初始化 tableNode:
// 1
_tableNode = [[ASTableNode alloc]initWithStyle:UITableViewStylePlain];
// 2
self.tableNode.dataSource = self;
self.tableNode.delegate = self;
// 3
self.tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone;
// 4
self.tableNode.view.leadingScreensForBatching = 1.0;
// 5
[self.view addSubnode:self.tableNode];
代码解释如下:
实现一个viewWillLayoutSubviews方法:
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
self.tableNode.frame = CGRectMake(0, CGRectGetMaxY(_bar.frame), CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)-CGRectGetHeight(self.bar.frame));
}
没有什么,指定了 TabelNode 的 frame 而已。这里我们让它放在 _bar(一个 UIView)下面,并占据屏幕上除 _bar 以外的剩余空间。
在 dealloc 中释放 delegate 和 dataSource,据说这样可以避免闪退(实际有没有作用不得而知):
-(void)dealloc{
self.tableNode.delegate = nil;
self.tableNode.dataSource = nil;
}
接下来实现 dataSource:
- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section {
// 1
return self.models.count;
}
- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 2
AlbumModel* model = _models[indexPath.row];
// 3
ASCellNode *(^ASCellNodeBlock)() = ^ASCellNode *() {
AlbumCellNode *cellNode = [[AlbumCellNode alloc] initWithAlbum:model];
return cellNode;
};
return ASCellNodeBlock;
}
- (NSInteger)numberOfSectionsInTableNode:(ASTableNode *)tableNode{
// 4
return 1;
}
ASTableNode 的数据源方法和 UITableView 的数据源方法基本上是可以对应的,比如这 3 个方法,返回了表格的行数、节数和 cell。
代码解释如下:
接下来是 delegate 方法实现:
// 1
- (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode {
return YES;
}
//2
- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context
{
[context beginBatchFetching];
[self loadPageWithContext:context];
}
// 3
- (void)tableNode:(ASTableNode *)tableNode didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableNode deselectRowAtIndexPath:indexPath animated:YES];
// 你自己的代码
......
}
这两个全新的 delegate 方法是 ASTableDelegate 中独有的,UITableViewDelegate 中没有对应的方法。这两个方法的作用是让表格实现异步加载,也就是以往我们需要用 MJRefresh 实现的功能。代码解释如下:
然后,新增两个方法如下:
// 1
- (void)loadPageWithContext:(ASBatchContext *)context
{
NSString* radioId = [[AccountAdditionalModel currentAccount] radioId];
// 2
[self loadAlbums:radioId pageNum:pageNum+1 pageSize:pageSize].thenOn(dispatch_get_main_queue(),^(NSArray<AlbumModel>* array){
if(array!=nil){
// 3
pageNum = pageNum+1;
[_models addObjectsFromArray: array];
// 4
[self insertNewRowsInTableNode:array];
}
// 5
if (context) {
[context completeBatchFetching:YES];
}
}).catch(^(NSError* error){
[self showHint:error.localizedDescription];
// 6
if (context) {
[context completeBatchFetching:YES];
}
});
}
// 7
- (void)insertNewRowsInTableNode:(NSArray<AlbumModel>*)array
{
NSInteger section = 0;
NSMutableArray *indexPaths = [NSMutableArray array];
for (NSUInteger row = _models.count-array.count; row < _models.count; row++) {
NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section];
[indexPaths addObject:path];
}
[_tableNode insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
}
代码解释如下:
ASTableNode 也有“页”的概念,但和我们向服务器进行分页请求时的“页”不同,这里的“页”是一屏的概念。也就是说,我们在 viewDidLoad 中设置 self.tableNode.view.leadingScreensForBatching = 1.0; 这一句时,表示当屏幕中还剩下一屏(页)的数据就要显示完的时候,ASTableNode 会自动进行抓取。
但是我们一次向服务器能够请求的页大小并不一定能够填满一屏。比如分页查询的页大小是 4,然而 4 条数据并不足以填满一个屏幕,因此 ASTableNode 还会再请求一次分页查询,然后检查(会进行一个预布局,计算数据显示时的尺寸)是否填满一屏,如果不够,会再次请求,直至填满一屏。
也就是说,数据在真正得到显示之前就已经进行了布局(异步的)。当需要显示的时候,仅仅是一个绘制而已,这样绘制的速度就会非常快,滚动体验会无比顺滑。
ASTableNode 的真正难点,在于自定义 cell 的使用,因为 ASDK 使用了自己的布局系统 ASLayoutSpec,这个布局系统不同于你曾经使用过的任何一个自动布局系统,没有使用过它的人无疑会很头疼。但没有关系,当你使用过它几次之后,你就会觉得它和安卓的布局系统很像,还是较容易掌握的。
在前面的 tableNode: nodeBlockForRowAtIndexPath: 数据源方法中,我们使用了一个自定义的 ASCellNode 类 AlbumCellNode,它就是一个 ASLayoutSpec 的极好的例子。我们来看看它是怎样实现的。
@implementation AlbumCellNode{
AlbumModel* _album;
ASNetworkImageNode *_photoImageNode;
ASTextNode *_titleLabel;
ASTextNode *_contentLabel;
ASTextNode *_albumCountLabel;
}
这个 cell 中包含了几个对象:
然后是初始化方法:
- (instancetype)initWithAlbum:(AlbumModel *)album{
self = [super init];
if (self) {
_album = album;
// 1
_photoImageNode = [[ASNetworkImageNode alloc] init];
_photoImageNode.URL = [NSURL URLWithString:[kDownloadUrl add: album.icon]];
[self addSubnode:_photoImageNode];
// 2
_titleLabel = [[ASTextNode alloc]init];
_titleLabel.attributedText = [[NSAttributedString alloc]initWithString:album.title attributes:@{NSForegroundColorAttributeName: [UIColor blackColor],
NSFontAttributeName: [UIFont systemFontOfSize:15]}];
[self addSubnode:_titleLabel];
_contentLabel = [[ASTextNode alloc]init];
NSString* newStr = album.content;//[album.content stringByPaddingToLength:album.content.length*10 withString:album.content startingAtIndex:0];
_contentLabel.attributedText = [[NSAttributedString alloc]initWithString:newStr attributes:@{NSForegroundColorAttributeName: [UIColor grayColor],
NSFontAttributeName: [UIFont systemFontOfSize:12]}];
[self addSubnode:_contentLabel];
_albumCountLabel = [[ASTextNode alloc]init];
_albumCountLabel.backgroundColor = [UIColor redColor];
_albumCountLabel.cornerRadius = 3;
_albumCountLabel.clipsToBounds = YES;
_albumCountLabel.attributedText = [[NSAttributedString alloc]initWithString:[album.programCount add:@"辑"] attributes:@{NSForegroundColorAttributeName: [UIColor whiteColor],
NSFontAttributeName: [UIFont systemFontOfSize:9]}];
[self addSubnode:_albumCountLabel];
}
return self;
}
这个初始化方法需要用一个模型数据作为参数,根据模型中已有的数据来初始化 UI 组件。
注意到一个细节没有?不管是 ASNetworkImageNode 还是 ASTextNode,它们都不需要设置框架(frame)。这是因为它们的构建和布局是分开进行的(这就是框架名字中 Async 异步的由来了),在初始化方法中,你只管构建好了,布局在另一个方法中进行:
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
// 1
NSMutableArray *mainStackContent = [[NSMutableArray alloc] init];
[mainStackContent addObject:_titleLabel];
[mainStackContent addObject:_contentLabel];
// 2
_albumCountLabel.textContainerInset = UIEdgeInsetsMake(3, 5, 3, 5);
// 3
ASRelativeLayoutSpec *albumCountSpec = [ASRelativeLayoutSpec
relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
verticalPosition:ASRelativeLayoutSpecPositionCenter
sizingOption:ASRelativeLayoutSpecSizingOptionMinimumWidth
child:_albumCountLabel];
// 4
[mainStackContent addObject:albumCountSpec];
// 5
ASStackLayoutSpec *contentSpec =
[ASStackLayoutSpec
stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
spacing:8.0
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStretch
children:mainStackContent];
contentSpec.style.flexShrink = 1.0;
// 6
_photoImageNode.style.preferredSize = CGSizeMake(90, 90);
// 7
ASStackLayoutSpec *avatarContentSpec =
[ASStackLayoutSpec
stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:16.0
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStart
children:@[_photoImageNode, contentSpec]];
// 8
return [ASInsetLayoutSpec
insetLayoutSpecWithInsets:UIEdgeInsetsMake(8, 16, 8, 16)
child:avatarContentSpec];
}
所有的 ASNode 都必须在这个方法中进行布局,这也是这一节重点要介绍的地方:
ASDK 有 4 中布局,这 4 种布局分别是:
在这个例子中我们用到了 3 种。Overlay 布局不太常用,它类似于 css 中的 position : absolute 属性,允许将一个布局重叠到另一个布局上。
好了,本文就此结束。本文偏向于 ASDK 的应用和实作,对 ASDK 的基础概念介绍得较少。如果你需要这方面的介绍,可以阅读这几篇文章:
AsyncDisplayKit 2.0 Objective-C 教程
标签:添加 相对 code stack asi targe section 结束 tac
原文地址:http://blog.csdn.net/kmyhy/article/details/55656939