迁移老文章到掘金 文档比较老了,不适用最新的2.0html
最近在拆解学习AsyncDisplayKit这个很知名的轮子,发现这个轮子内容仍是很是庞大的,想要分解学习以前必然要先对这个项目如何使用如何工做有一个初步的概念,因此动手准备把官方文档简单的翻译一下,但愿更多看不顺英文文档的人,能有个简单的粗略了解,有了这个粗略了解以后,打算再动手准备进行源码分析react
彻底没有翻译文档的经验,碰到一些用词不合适的时候,仍是推荐对比这原文进行观看c++
有些名词,由于水平实在不佳,翻译过来后反而会让人混乱,产生误解,对照英文去看,感受会清晰一些,哎╮(╯_╰)╭git
若是你们愿意,能够一块儿帮忙修改,直接提prgithub
AsyncDisplayKitDocTranslation Github地址swift
AsyncDisplayKit的基础单元是Node,ASDisplayNode是UIView的抽象,就好像UIView是CALayer的抽象,可是不一样于Views只能在主线程使用,Nodes是线程安全的,你能够并行在后台线程,实例化他们,配置他们总体的层次结构等数组
iOS设备有一条黄金准则,想要保持用户交互的流畅和快速响应,你的app必须保证渲染达到每秒60帧。意思就是主线程只有1/60秒的时间来推进每一帧,执行全部布局和绘图代码的时间只有16毫秒!并且因为一些系统级别的开销,你的布局绘图代码通常状况超过10毫秒,就可能引发掉帧xcode
AsyncDisplayKit可让你把图形解码,文本计算,渲染,等其余UI开销的操做移出主线程,还有一些其余的小把戏,咱们后面会降到缓存
若是你以前使用过views,那么你应该已经知道如何使用nodes,大部分的方法都有一个等效的node,大部分的UIView和CALayer的属性都有相似的可用的。任何状况都会有一点点命名差别(例如,clipsToBounds和masksToBounds),node基本上都是默认使用UIView的名字,惟一的例外是node使用position而不是center
固然,你也能够直接访问底层view和layer,使用node.view和node.layer
一些AsyncDisplayKit核心节点包括:
当在项目中替换使用AsyncDisplayKit的时候,一个常常犯的错误就是把一个Node节点直接添加到一个现有的view视图层次结构里。这样作会致使你的节点在渲染的时候会闪烁一下
相反,你应该你应该把nodes节点,当作一个子节点添加到一个容器类里。这些容器类负责告诉所包含的节点,他们如今都是什么状态,以便于尽量有效的加载数据与渲染。你能够把这些类当作UIKit和ASDK的整合点
下面有4种节点容器
AsyncDisplayKit的排版引擎是很是强大而且独特的,基于CSS FlexBox模型。他提供了一种声明方式来约定自定义节点所属的子节点的大小和布局,当全部的节点同时被默认渲染和展示的时候,经过给每一个节点提供一个ASLayoutSpec,异步的测量和布局。
这套排版引擎那个是基于ASLayouts的概念,他包含了位置,大小,ASLayoutSpecs等信息,ASLayoutSpecs定义了不一样的布局概念用于计算输出ASLayout结果,排版区域最终由子节点和其余排版区域一同决定
其余排版区域包括:
AsyncDisplayKit是一个UI框架,最初诞生于Facebook App。最开始Facebook团队面临一个很核心的问题:怎么能保证主线程尽量的简洁,AsyncDisplayKit就是答案。
如今,不少应用程序都会频繁使用手势以及物理动画,再不济,你的app也会很普遍使用某种形式的滚动试图,这类型的用户交互是彻底决定于主线程的流畅,而且对主线程的负荷十分敏感,一个被阻塞的主线程就意味着掉帧,卡顿,意味着很不愉快的用户体验
AsyncDisplayKit的Node节点就是一个线程安全的抽象对象,基于UIView和CALayer
[图就不翻译了]
当使用node节点的时候,你能够直接访问大部分的view和layer的属性,惟一区别是,当使用正确的时候,nodes经过异步进行计算,布局,最后默认同时进行渲染
想看更多地异步性能提高,请查看example/ASDKgram app,这个工程对比了基于UIKit的社交媒体demo,和基于ASDK的社交媒体demo
在iPhone6+上的性能提高不是很明显,可是在4S上,性能差距很是之大
AsyncDisplayKit所带来的性能提高,可让你为每一个用户在全部设备上,在全部的网络环境下,提供强大的用户体验设计
AsyncDisplayKit同样也在追求开发人员的使用体验,追求iOS&tvOS跨平台的特性,追求swift&OC语言的兼容性。只须要不多的代码就能构建很棒的app,清晰的架构,健壮的代码(参见examples/ASDKgram这个例子)(开发这个的都是一些超级聪明工做3年多的工程师)
随着AsyncDisplayKit逐渐成熟,不少聪明的工程师都加入一块儿构建这个项目,能够大幅度节省做为开发者,使用ASDK的开发时间
先进技术
ASRunLoopQueue
ASRangeController 智能预加载
网络工具
automatic batch fetching (e.g. JSON payloads)自动批量抓取
CocoaPods安装 AsyncDisplayKit可使用cocoapods安装,将下面的代码添加进入Podfile
pod 'AsyncDisplayKit'
Carthage安装 AsyncDisplayKit可使用Carthage安装,将下面的代码添加进入Cartfile
github "facebook/AsyncDisplayKit"
在终端执行carthage update
来构建AsyncDisplayKit库,会自动在项目根目录下生成Carthage名字的文件夹,里面有个build文件夹,能够用来framework到你打算使用的项目中
静态库 AsyncDisplayKit能够当作静态库引入
1)拷贝整个工程到你的目录下,添加AsyncDisplayKit.xcodeproj
到你的workspace
2)在build phases中,在Target Dependencies下添加AsyncDisplayKit Library
3)在build phases中,添加libAsyncDisplayKit.a, AssetsLibrary, Photos等框架到Link Binary With Libraries中
4)在build settings中,添加-lc++
和-ObjC
到 project linker flags
引用AsyncDisplayKit
#import <AsyncDisplayKit/AsyncDisplayKit.h>
复制代码
node的功能很强大的缘由是具备异步渲染和计算的能力,另外一个相当重要的因素是ASDK的智能预加载方案。
正如在准备开始
提到的那样,使用一个在节点容器上下文以外的节点是很是的没有意义的,这是由于全部的节点都有一个当前界面状态的概念。
这个界面状态的属性是不断的经过ASRangeController来进行更新,ASRangeController是由容器建立和内部维护的。
一个节点在容器以外被使用,是不会使它的状态获得rangeController的更新。有时候会产生的闪屏现象是因为,一个节点刚刚被渲染到屏幕以后又进行了一次渲染
当节点被添加到滚动或者分页的控件的时候,通常都会遇到下面的几种状况。这就是说scrollview正在滚动,他的界面属性会随着移动一直在更新。 每一个节点必定会处于如下几种范围:
数据范围:距离显示最远的区域,他们的数据已经收集准备完毕,不管是经过本地磁盘仍是API,但远没有进行显示处理 显示范围:在这个范围,显示任务,好比文本光栅化,图像解码等正在进行 可见范围:这个node,至少有一个像素已经被渲染到屏幕上
每个范围的大小都是全屏去计算的,通常状况下,默认的尺寸就能很好的工做,可是你也能够经过设置滚动节点的区域类型参数,很简单的进行调整。
[图就不翻译了,红色是可见范围,橙色是显示范围,黄色是数据范围]
上图就是一款app的截图,用户能够向下滚动,正如你所看到的,用户滚动方向区域(领先方向)的大小,要比用户离开方向区域(尾随方向)的大小,大得多。若是用户滚动的方向发生了改变,这两个区域的大小会动态的切换,以保证内存的最佳使用。你只须要考虑定义领先方向和尾随方向的区域大小,而没必要担忧滚动方向的变化。
在这个截图中,你还能够看到智能的预加载是如何在多维度下工做的,你能够看到一个垂直的滚动容器,你能够看到虽然有些node还未在红色的设备屏幕中出现,可是他有一个范围控制器,也有处在黄色的数据范围的node,和橙色的显示范围的node
随着用户的滚动,nodes会移动穿过这些区域,并作出适当的反应,读取数据,渲染,等等。你本身建立的node子类能够很容易的经过实现相应的回调来精细设计他们
可见范围回调
- (void)visibilityDidChange:(BOOL)isVisible;
显示范围回调
- (void)displayWillStart
- (void)displayDidFinish
数据范围回调
- (void)fetchData
- (void)clearFetchedData
最后,千万别忘记调用super
写一个node的子类,很像写一个UIView的子类,这有几条准则须要遵照,来确保你可以充分发挥这个框架的潜力,确保你的节点正常工做
-init
一般状况下,你都会写一些init方法,包括,调用[super init]
,而后设置一些自定义的属性。这里须要记住的尤为重要的一点,你写的(node的)init方法必须能被任何的队列和线程调用,因此尤为要注意的一点就是,在init方法里确保不要初始化任何UIKit的对象
-didLoad
这个方法很像UIViewController的-viewDidLoad
,这个时间点支持视图已经被加载完毕,此时能够保证是主线程,所以,你能够在这个时机,设置UIKit相关对象
layoutSpecThatFits
这个方法就是用来创建布局规则对象,产生节点大小以及全部子节点大小的地方,你建立的布局规则对象一直持续到这个方法返回的时间点,通过了这个时间点后,它就不可变了。尤为重要要记住的一点事,千万不要缓存布局规则对象,当你之后须要他的时候,请从新建立
layout
在这个方法里调用super以后,布局规则对象会把全部的子节点都计算而且定位好,因此这个时间点是你手动进行布局全部子view的时机。或许更有用的是,有时候你想手动布局,但并不太容易建立一个布局规则对象,或者有时候你不想等全部子节点布局完毕,而只是很简单的手动设置frame,若是是这样的话,就在这个方法里写
AsyncDisplayKit的排版引擎是基于CSS Box模型,它具备相似UIKit(自动布局)同样的特征,一旦你习惯它,就会发现这是他最有用的特色。只要有足够的联系,你就会愈来愈习惯经过建立布局声明来实现基础约束。
想要参与这个过程的主要方式就是经过在子类中实现layoutSpecThatFits:
方法,在这里你声明和创建布局规则对象,返回最重的包含全部信息的对象
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec];
verticalStack.direction = ASStackLayoutDirectionVertical;
verticalStack.spacing = 4.0;
[verticalStack setChildren:_commentNodes];
return verticalStack;
}
复制代码
尽管这个例子很是简单,可是它给了你一个思路,如何去使用布局规则对象,一个stack布局规则对象为例,定义了一种子节点们相邻的布局方式,肯定了方向,间隔的定义,他看起来很像UIStackView,可是能够支持低版本iOS
布局规则对象的孩子,能够是任何对象,只要他听从<ASLayoutable>
协议,全部的nodes和布局规则对象都听从这个协议,就是说你的布局,能够用任何你想的对象来创建。
好比,你想要添加8像素的间隔给这个你刚刚创建好的stack
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec];
verticalStack.direction = ASStackLayoutDirectionVertical;
verticalStack.spacing = 4.0;
[verticalStack setChildren:_commentNodes];
UIEdgeInsets insets = UIEdgeInsetsMake(8, 8, 8, 8);
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets
child:verticalStack];
return insetSpec;
}
复制代码
你能够很容易就完成
固然,使用布局规则对象须要一些联系,能够看layout section部分
强烈建议你经过节点容器,来使用AsyncDisplayKit的节点,AsyncDisplayKit提供了下面4种节点
例子demo在每一个节点容器的文档中被高亮展现了
想要详细的从UIKit移植app到AsyncDisplayKit信息,请看移植指南
介电容器自动管理着子节点的智能预加载,全部的子节点均可以异步的执行布局计算,获取数据,解码,渲染。正由于此,推荐在节点容器内使用节点
注意:确实能够直接使用节点不使用节点容器,但他们只会在出现到屏幕上的时候展示一次,这样会致使性能退化,或者闪烁
ASViewController是UIViewController的子类,而且添加了不少和ASDisplayNode层级相关的功能
ASViewController能够被当作UIViewController来使用,包括套在一个UINavigationController,UITabBarController,UISpitViewController里面
使用ASViewController的一大好处是节省内存,当ASViewController离开屏幕的时候会自动减小其子node的数据范围(前文提到)和显示范围(前文提到),这是大型app管理的关键
除此以外还有更多地功能,把它当作你app的基础ViewController的基类是一个不错的选择。
UIViewController提供他本身的view,ASViewController提供了他本身的node,是在-initWithNode:
方法中建立的
你能够参考下面这个ASViewController的子类PhotoFeedNodeController,在ASDKgram sample app里面,你能够看到他是如何管理和使用一个列表节点的
这个列表节点就是在-initWithNode:
方法中建立的
- (instancetype)init
{
_tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
self = [super initWithNode:_tableNode];
if (self) {
_tableNode.dataSource = self;
_tableNode.delegate = self;
}
return self;
}
复制代码
若是你的app已经有了很复杂的viewcontroller层级,你最好把他们都改为继承自ASViewController,就是说,即使你不使用ASViewController的-initWithNode:
方法,你只是把它当作传统的UIViewController来使用,当你一旦选择不一样的使用方式,他就能给你节点管理方面的支持
ASTableNode等效于UIKit的UITableview,并且能够拿来替换UITableView
ASTableNode替换UITableView如下方法 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
替换成下面这两种方法之一
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath
建议你使用nodeBlock的版本,以便这些节点能够同时进行准备和显示处理,全部的子节点均可以在后台线程进行初始化,因此必定要保持他们的代码是线程安全的。
这两个方法都须要返回一个ASCellNode或者ASCellNodeBlock。一个ASCellNodeBlock是用来在后台建立ASCellNode的,注意ASCellNode在ASTableNode,ASCollectionNode,ASPagerNode都有使用
请注意这两个方法都不须要重用机制!
AsyncDisplayKit并无提供一个相似UITableViewController的东西,你须要使用ASViewController初始化一个ASTableNode
继续能够参照ASViewController的子类PhotoFeedNodeController,在ASDKgram sample app里面
一个ASTableNode在ASViewController的-initWithNode:
方法初始化
- (instancetype)init
{
_tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
self = [super initWithNode:_tableNode];
if (self) {
_tableNode.dataSource = self;
_tableNode.delegate = self;
}
return self;
}
复制代码
保证node block 的代码必定要是线程安全的,一方面要保证块里面的数据对外面是可访问的,因此你不该该使用block内的索引
继续参考子类PhotoFeedNodeController,在ASDKgram sample app里面的例子,-tableView:nodeBlockForRowAtIndexPath:
这个方法
这个例子能够看出来,如何在使用索引,访问photo的模型
- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath
{
PhotoModel *photoModel = [_photoFeed objectAtIndex:indexPath.row];
// this may be executed on a background thread - it is important to make sure it is thread safe
ASCellNode *(^cellNodeBlock)() = ^ASCellNode *() {
PhotoCellNode *cellNode = [[PhotoCellNode alloc] initWithPhoto:photoModel];
cellNode.delegate = self;
return cellNode;
};
return cellNodeBlock;
}
复制代码
一个很重要的事情就是,ASTableNode并不提供相似UITableview的-tableView:heightForRowAtIndexPath:
方法
这是由于节点基于本身的约束来肯定本身的高度,就是说你再也不须要写代码来肯定这个细节
一个node经过-layoutSpecThatFits:
方法返回的布局规则肯定了行高,全部的节点只要提供了约束大小,就有能力本身肯定本身的尺寸
在默认的状况下,tableNode提供了Cell的尺寸约束范围,最小宽度和最低高度是0,最大宽度是tablenode的宽度,最大高度是MAX_FLOAT,这就是说,tablenode的cell,老是填满tablenode的全宽,他的高度是本身计算自适应的
若是你对一个ASCellNode调用了setNeedsLayout
,他会自动的把布局传递,若是总体所须要的大小发生了变化,表会被告知要进行更新
和UIKit不一样的时,你不须要调用reload,这样很节省了代码,能够看ASDKgram sample来看一个table的具体实现
ASCollectionNode就是相似UIKit的UICollectionView,能够拿来代替UICollectionView
ASCollectionNode替换UICollectionView的时候须要把下面这个方法
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
替换成下面2个之一
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
同tablenode同样,推荐使用block的版本
正如前面说过的
AsyncDisplayKit并无提供相似UICollectionViewController的类,你仍是须要使用ASViewController,初始化的时候建立一个ASCollectionNode,在-initWithNode:
方法里
- (instancetype)init
{
_flowLayout = [[UICollectionViewFlowLayout alloc] init];
_collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:_flowLayout];
self = [super initWithNode:_collectionNode];
if (self) {
_flowLayout.minimumInteritemSpacing = 1;
_flowLayout.minimumLineSpacing = 1;
}
return self;
}
复制代码
ASTableNode,ASPagerNode都是这样工做的
若是你用过之前版本的ASDK,你会发现,为了方便ASCollectionNode,ASCollectionView已经被移除了,
ASCollectionView是UICollectionView的子类,仍然是经过ASCollectionNode内部来使用。虽然他不能够被直接建立,可是他能够经过ASCollectionNode的.view属性来访问,可是别忘了,一个节点的视图,只有在viewdidload 或者didload以后,才能够进行访问
下面的这个例子能够看出来,直接访问ASCollectionView
- (void)viewDidLoad
{
[super viewDidLoad];
_collectionNode.view.asyncDelegate = self;
_collectionNode.view.asyncDataSource = self;
_collectionNode.view.allowsSelection = NO;
_collectionNode.view.backgroundColor = [UIColor whiteColor];
}
复制代码
就像以前提到过的,ASCollectionNode和ASTableNode不须要计算他们的CellNode的高度
如今,不管采用哪一种UICollectionViewLayout,cell能够根据约束的大小来自动布局自适应,
不久以后,会有一个相似ASTableNode的constrainedSizeForRow:
方法,可是如今,若是你须要在collectionNode里约束一个cellNode,你须要包装处理一下约束规则对象
最详细的ASCollectionNode例子就是CustomCollectionView,包括自定义的ASCollectionNode与UICollectionViewLayout.
更多地demo请看
ASPagerNode是ASCollectionNode的子类,他特别定制了UICollectionViewLayout
使用ASPagerNode可让你开发相似UIKit中UIPageViewController的效果,ASPagerNode目前只支持在滚动停留到正确的位置,但还不支持循环滚动
最核心的数据源方法以下
- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode
- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index
- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index
后面这两种方法就像ASCollectionNode 和 ASTableNode同样须要返回ASCellNode 或者ASCellNodeBlock,用于建立能够在后台线程运行的ASCellNode
注意,这些方法都不要写重用逻辑,不像UIKit,这些方法在即将要显示的时候,是不会调用的
-pagerNode:nodeAtIndex:
会在主线程被调用, -pagerNode:nodeBlockAtIndex:
更推荐使用,由于全部的node的初始化方法均可能在背景线程和主线程中调用,因此必定要确保block中的代码线程安全
保证node block 的代码必定要是线程安全的,一方面要保证块里面的数据对外面是可访问的,因此你不该该使用block内的索引
能够看下面的例子
- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index
{
PhotoModel *photoModel = _photoFeed[index];
// this part can be executed on a background thread - it is important to make sure it is thread safe!
ASCellNode *(^cellNodeBlock)() = ^ASCellNode *() {
PhotoCellNode *cellNode = [[PhotoCellNode alloc] initWithPhoto:photoModel];
return cellNode;
};
return cellNodeBlock;
}
复制代码
一个颇有效的方式是,直接返回ASViewController中初始化好的ASCellNode,因此仍是推荐使用ASViewController
- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index
{
NSArray *animals = self.animals[index];
ASCellNode *node = [[ASCellNode alloc] initWithViewControllerBlock:^{
return [[AnimalTableNodeController alloc] initWithAnimals:animals];;
} didLoadBlock:nil];
node.preferredFrameSize = pagerNode.bounds.size;
return node;
}
复制代码
在这个例子这种,你能够看到节点经过initWithViewControllerBlock
方法进行约束,为了正确的布局,仍是须要提供一个但愿的framesize
例子
ASDisplayNode是最主要的UIView和CALayer的抽象对象,他初始化的时候拥有一个UIView,同时UIView在初始化的时候拥有一个CALayer
ASDisplayNode *node = [[ASDisplayNode alloc] init];
node.backgroundColor = [UIColor orangeColor];
node.bounds = CGRectMake(0, 0, 100, 100);
NSLog(@"Underlying view: %@", node.view);
复制代码
Node和UIView具备同样的属性,因此使用起来很是像UIKit
不管是view仍是layer的属性,均可以经过node进行访问
ASDisplayNode *node = [[ASDisplayNode alloc] init];
node.clipsToBounds = YES; // not .masksToBounds
node.borderColor = [UIColor blueColor]; //layer name when there is no UIView equivalent
NSLog(@"Backing layer: %@", node.layer);
复制代码
你能够看到,默认命名是照着UIView的习惯,除非这个属性是UIView不具有的,你就像处理普通UIView同样,去访问底层的CALayer
当咱们使用了节点容器,节点的属性会在背景线程被设置和使用,背后的view/layer会延迟懒加载生成约束,你不须要去担忧跳入背景线程要注意什么,由于框架都处理好了,可是你也要了解都发生了什么
某些状况下,须要初始化一个节点,提供一个view当作基础view。这些view须要一个block来处理以后被保存的view(有点绕。。我也没太懂。)
这些节点展现的步骤是同步的,这是由于节点只有在被包上一层_ASDisplayView后,才能够异步,如今他只是包在普通UIView上
ASDisplayNode *node = [ASDisplayNode alloc] initWithViewBlock:^{
SomeView *view = [[SomeView alloc] init];
return view;
}];
复制代码
这样可让你把一个UIView子类包装成一个ASDisplayNode子类
惟一要注意的时node使用position,不是center
ASCellNode 多是最经常使用的节点子类了,他能够被用于ASTableNode和ASCollectionNode
就像其余子类继承自ASDisplayNode,ASCellNode更重要的是须要重写-init
方法初始化 -layoutSpecThatFit:
来布局和计算,若是须要,重写-didLoad
来添加额外的手势或者额外的布局
若是你不喜欢继承,你也可使用-initWithView
和-initWithViewController
方法来返回一个节点,他的内部view就是经过已经存在的view来建立的
敬请期待......
敬请期待......
点击区域可视化
只用一行代码,就能够轻松的把全部的ASControlNode的点击区域可视化,经过这个工具hit test slop debug tool.
ASButtonNode是ASControlNode的子类,提供了简单button的功能,有多重状态,标签,图片,和布局选项,开启layerBacking能够显著减小button对主线程的影响
功能:
当心: 选择selected属性的逻辑应该由开发者处理,点击buttonNode不会自动的开启selected
敬请期待......
ASEditableTextNode提供了一个灵活的高效的动画有好的可编辑文本控件
功能:sizeRange比constrainedSize容许更大的长文本,支持占位符 当心:不支持 layer backing 例子:examples/editableText
敬请期待......
ASImageNode性能提示
只须要一行代码就能够方便的查看app没有下载和渲染的过量图形数据,或者低质量的图形数据,使用这个工具pixel scaling debug tool.
ASNetworkImageNode用来展现那些被远程存储的图片,全部你要作的只是设置好URL,须要是一个NSURL实例,而且图片会异步的被夹在,正确的被读取
ASNetworkImageNode *imageNode = [[ASNetworkImageNode alloc] init];
imageNode.URL = [NSURL URLWithString:@"https://someurl.com/image_uri"];
复制代码
一个网图节点在尚未真实地大小的时候,是有必要指定一个特定得布局的
方法1:preferredFrameSize
若是你有一个标准大小,你但愿这个image节点能够按着布局,你可使用这个属性
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constraint
{
imageNode.preferredFrameSize = CGSizeMake(100, 200);
...
return finalLayoutSpec;
}
复制代码
方法2:ASRatioLayoutSpec
这个场景是一个绝好的使用ASRatioLayoutSpec的场景,不去设置静态的大小,你能够指定当图像下载完成时,和图像保持比例,
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constraint
{
CGFloat ratio = 3.0/1.0;
ASRatioLayoutSpec *imageRatioSpec = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:ratio child:self.imageNode];
...
return finalLayoutSpec;
}
复制代码
若是你不打算引入PINRemoteImage和PINCache,你会失去对jpeg的更好的支持,你须要自行引入你本身的cache系统,须要听从ASImageCacheProtocol
得益于PINRemoteImage,网图节点能够全面支持,有进度下载的JPEG图片,若是你的服务器提供这个功能,你的图片就能够展现的很是快,先加载低质量图,慢慢展现
逐步加载图片是很重要的,若是服务器被要求使用普通的JPEGS,可是给你提供了多个版本的图片数据,你可使用ASMultiplexImageNode
ASNetworkImageNode使用PINCache 来自动处理网络图片缓存
若是你不能使用渐进式JPEG,可是你能够处理同一个图的几个不一样尺寸的图形数据,你可使用ASMultiplexImageNode代替ASNetworkImageNode
在下面的例子里,你就是在CellNode里用了一个多图形节点,初始化以后,你一般须要作2个事情,
第一,确保downloadsIntermediateImages设置为YES,这样方便快速下载
第二,分配一个数组里面对应着图片类型key 和 value,这样将帮助节点选择下载哪一个URL,尝试加载
- (instancetype)initWithURLs:(NSDictionary *)urls
{
...
_imageURLs = urls; // something like @{@"thumb": "/smallImageUrl", @"medium": ...}
_multiplexImageNode = [[ASMultiplexImageNode alloc] initWithCache:nil
downloader:[ASBasicImageDownloader sharedImageDownloader]];
_multiplexImageNode.downloadsIntermediateImages = YES;
_multiplexImageNode.imageIdentifiers = @[ @"original", @"medium", @"thumb" ];
_multiplexImageNode.dataSource = self;
_multiplexImageNode.delegate = self;
...
}
复制代码
而后,若是你已经设置好了一个字典来保存已有的KEY和URL,你就能够简单的返回URL给对应的KEY
#pragma mark Multiplex Image Node Datasource
- (NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode
URLForImageIdentifier:(id)imageIdentifier
{
return _imageURLs[imageIdentifier];
}
复制代码
也有一个delegate能够方便你显示下载进度,展现的时候,你能够根据需求更新你的方法
好比,下面的例子,当你一个新的图片下载完成后,你须要一个回调
#pragma mark Multiplex Image Node Delegate
- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode
didUpdateImage:(UIImage *)image
withIdentifier:(id)imageIdentifier
fromImage:(UIImage *)previousImage
withIdentifier:(id)previousImageIdentifier;
{
// this is optional, in case you want to react to the fact that a new image came in
}
复制代码
ASMapNode提供了一个完整的异步准备,自动预加载,高效内存处理的节点 ,他的标准模式下,是异步快照的形式,ASTableView 和 ASCollectionView 会自动触发liveMap模式,liveMode模式能够轻松的提供缓存,这是地图交互所必须的
功能:使用MKMapSnapshotOptions所规定的主要形式,容许无缝过分地图快照和3D相机模式,容许滚动时候自动异步加载
不足:MKMapView 不是线程安全的
ASVedioNode是一个新的功能,而且专为方便高效的在滚动试图里嵌入视频
功能:当对象可见,支持自动播放,哪怕是他们在滚动容器内(ASPagerNode or ASTableNode),若是提供了URL缩略图占位符能够异步下载,若是不提供也能够将解码第一帧当作展位图
不足:必须使用AVFoundation 这个库
例子:examples/videosTableview - examples/videos
敬请期待......
ASLayout是一个自动的,异步的,纯OC盒子模型排版的布局功能,是一种CSS flex box的简单版,ComponetKit的简化版本,他的目的是让你的布局居右可扩展和复用性
UIView的实例存储位置,大小是经过center和bounds的属性,当约束条件发生变化,CoreAnimation会调用layoutSubviews,告诉view须要更新界面
实例(全部的ASDisplayNodes和子类)不须要大小和位置信息,相反,AsyncDisplayKit会调用layoutSpecThatFits方法经过给一个约束来描述大小和位置信息
术语可能有点混乱,因此在这里对全部ASDK自动布局进行简单的说明:
协议定义了测量物体布局的方法,符合协议的对象就有相关的能力。经过ASLayout返回的值,必须有2个前提要肯定,1,layoutable对象的大小(不必定是位置),2其全部子节点的大小与位置。经过递推树来让父节点计算布局肯定最终的结果。
这个协议是全部布局相关协议的家,包含全部的ASXXLayoutSpec协议,这些协议包含着布局的规则和选项,例如,ASStackLayoutSpec具备限定layoutable如何缩小或放大根据可用空间的做用。这些布局选项都保存在ASLayoutOptions类,通常来讲你不须要担忧布局选项。若是要建立自定义布局规则,你能够扩展去适应新的布局选项。
全部的ASDisplayNodes和他的子类,以及ASLayoutSpecs都符合这个协议
一个ASLayoutSpec是一个不可变的描述布局的对象,布局规范的建立要经过layoutSpecThatFits:方法,一个布局规范的建立和修改,一旦它传给ASDK,全部的属性都将变成不可变,而且任何设置改变都将致使断言
每一个ASLayoutSpec至少要做用在一个孩子上,ASLayoutSpec持有这个孩子,一些约束规则如ASInsetLayoutSpec,只须要一个孩子,其余的规则须要多个孩子。
你不须要了解ASLayout,只须要知道他表明着一个不变的布局树,并且经过遵循协议的对象返回
didLoad:
处理layoutSpecThatFits:
处理AsyncDisplayKit包含有一套布局的组件,下面的LayoutSpecs容许你能够拥有多个孩子
ASStackLayoutSpec 基于CSS Flexbox的一个简化版本,能够水平或者垂直的排布堆栈组件,并制定如何对其,如何适应空间
ASStaticLayoutSpec 容许你固定孩子的偏移
下面LayoutSpecs容许你布局单一的孩子
3个逐渐复杂的样例
[图不翻译了]
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constraint
{
ASStackLayoutSpec *vStack = [[ASStackLayoutSpec alloc] init];
[vStack setChildren:@[titleNode, bodyNode];
ASStackLayoutSpec *hstack = [[ASStackLayoutSpec alloc] init];
hStack.direction = ASStackLayoutDirectionHorizontal;
hStack.spacing = 5.0;
[hStack setChildren:@[imageNode, vStack]];
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(5,5,5,5) child:hStack];
return insetSpec;
}
复制代码
[图不翻译了]
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
// header stack
_userAvatarImageView.preferredFrameSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); // constrain avatar image frame size
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
spacer.flexGrow = YES;
ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack
headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to left side of header stack
headerStack.spacing = HORIZONTAL_BUFFER;
[headerStack setChildren:@[_userAvatarImageView, _userNameLabel, spacer, _photoTimeIntervalSincePostLabel]];
// header inset stack
UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER);
ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:headerStack];
headerWithInset.flexShrink = YES;
// vertical stack
CGFloat cellWidth = constrainedSize.max.width;
_photoImageView.preferredFrameSize = CGSizeMake(cellWidth, cellWidth); // constrain photo frame size
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec];
verticalStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space
[verticalStack setChildren:@[headerWithInset, _photoImageView, footerWithInset]];
return verticalStack;
}
复制代码
完整的ASDK工程能够查阅 example/ASDKgram
[图不翻译了]
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
ASLayoutSpec *textSpec = [self textSpec];
ASLayoutSpec *imageSpec = [self imageSpecWithSize:constrainedSize];
ASOverlayLayoutSpec *soldOutOverImage = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imageSpec
overlay:[self soldOutLabelSpec]];
NSArray *stackChildren = @[soldOutOverImage, textSpec];
ASStackLayoutSpec *mainStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
spacing:0.0
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStretch
children:stackChildren];
ASOverlayLayoutSpec *soldOutOverlay = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:mainStack
overlay:self.soldOutOverlay];
return soldOutOverlay;
}
- (ASLayoutSpec *)textSpec {
CGFloat kInsetHorizontal = 16.0;
CGFloat kInsetTop = 6.0;
CGFloat kInsetBottom = 0.0;
UIEdgeInsets textInsets = UIEdgeInsetsMake(kInsetTop, kInsetHorizontal, kInsetBottom, kInsetHorizontal);
ASLayoutSpec *verticalSpacer = [[ASLayoutSpec alloc] init];
verticalSpacer.flexGrow = YES;
ASLayoutSpec *horizontalSpacer1 = [[ASLayoutSpec alloc] init];
horizontalSpacer1.flexGrow = YES;
ASLayoutSpec *horizontalSpacer2 = [[ASLayoutSpec alloc] init];
horizontalSpacer2.flexGrow = YES;
NSArray *info1Children = @[self.firstInfoLabel, self.distanceLabel, horizontalSpacer1, self.originalPriceLabel];
NSArray *info2Children = @[self.secondInfoLabel, horizontalSpacer2, self.finalPriceLabel];
if ([ItemNode isRTL]) {
info1Children = [[info1Children reverseObjectEnumerator] allObjects];
info2Children = [[info2Children reverseObjectEnumerator] allObjects];
}
ASStackLayoutSpec *info1Stack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:1.0
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsBaselineLast children:info1Children];
ASStackLayoutSpec *info2Stack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:0.0
justifyContent:ASStackLayoutJustifyContentCenter
alignItems:ASStackLayoutAlignItemsBaselineLast children:info2Children];
ASStackLayoutSpec *textStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
spacing:0.0
justifyContent:ASStackLayoutJustifyContentEnd
alignItems:ASStackLayoutAlignItemsStretch
children:@[self.titleLabel, verticalSpacer, info1Stack, info2Stack]];
ASInsetLayoutSpec *textWrapper = [ASInsetLayoutSpec insetLayoutSpecWithInsets:textInsets
child:textStack];
textWrapper.flexGrow = YES;
return textWrapper;
}
- (ASLayoutSpec *)imageSpecWithSize:(ASSizeRange)constrainedSize {
CGFloat imageRatio = [self imageRatioFromSize:constrainedSize.max];
ASRatioLayoutSpec *imagePlace = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:imageRatio child:self.dealImageView];
self.badge.layoutPosition = CGPointMake(0, constrainedSize.max.height - kFixedLabelsAreaHeight - kBadgeHeight);
self.badge.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(0), ASRelativeDimensionMakeWithPoints(kBadgeHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kBadgeHeight)));
ASStaticLayoutSpec *badgePosition = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.badge]];
ASOverlayLayoutSpec *badgeOverImage = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imagePlace overlay:badgePosition];
badgeOverImage.flexGrow = YES;
return badgeOverImage;
}
- (ASLayoutSpec *)soldOutLabelSpec {
ASCenterLayoutSpec *centerSoldOutLabel = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY
sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY child:self.soldOutLabelFlat];
ASStaticLayoutSpec *soldOutBG = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.soldOutLabelBackground]];
ASCenterLayoutSpec *centerSoldOut = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:soldOutBG];
ASBackgroundLayoutSpec *soldOutLabelOverBackground = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:centerSoldOutLabel background:centerSoldOut];
return soldOutLabelOverBackground;
}
复制代码
完整的ASDK工程能够查阅 example/CatDealsCollectionView
使用ASC II Art 调试
ASLayoutSpecPlayground App
当使用ASDK的时候,你有3种布局选择,注意:UIKit的autolayout不支持
最原始的布局方式,相似于UIKit的布局方法,ASViewControllers使用这种布局方法
[ASDisplayNode calculateSizeThatFits:] vs. [UIView sizeThatFits:]
[ASDisplayNode layout] vs. [UIView layoutSubviews]
优点:(针对UIKit)
缺点:(针对UIKit)
有些时候,大幅度使用Layer而不是使用views,能够提升你的app的性能,可是手动的把基于view开发的界面代码改成基于layer的界面代码,很是的费劲,若是有时候由于要开启触摸或者view特定的功能的时候,你可能要功能回退
当你使用ASDK的node的时候,若是你打算把全部的view转换成layer,只须要一行代码
rootNode.layerBacked = YES;
若是你想回退,也只须要删除这一行,咱们建议不须要触摸处理的全部视图都开启
敬请期待......
预压缩,扁平化整个视图层级到一个图层,也能够提升性能,node也能够帮你作这件事
rootNode.shouldRasterizeDescendants = YES;
你的整个node层级都会渲染在一个layer下
敬请期待......
ASDisplayNode有一个hitTestSlop属性,是UIEdgeInsets,当这个值非零的时候,能够增长点击区域,更加方便进行点击
ASDisplayNode是全部节点的基类,因此这个属性能够在任何node上使用
注意: 这会影响-hitTest和-pointInside的默认实现,若是子类须要调用,请忽略
节点的触摸事件受到其父的边界+父HitTestSlop限制,若是想扩展父节点下的一个孩子节点的边界,请直接扩展父节点
ASDK的批量获取API能够很方便的让开发者获取大量新数据,若是用户滚动一个列表或者宫格的view,会自动的在特定范围内批量抓取,时机是由ASDK触发的
做为开发者,能够定义批量抓取的时机,ASTableView和ASCollectionView有个leadingScreensForBatching属性,用来处理这个,默认是2.0
为了实现批量抓取,必须实现2个delegate
- (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView
或者
- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView
在这两个方法你来决定当用户滚动到必定范围的时候,批量获取是否启动。通常是基于数据是否已经抓取完毕,或者本地操做来决定的
若是- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView,
返回NO,就不会产生新的批量抓取处理,若是返回YES,批量抓取就会开始,会调用下面2个方法
- (void)tableView:(ASTableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context;
或者
- (void)collectionView:(ASCollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context;
首先你要当心,这两个方法是在后台线程被调用的,若是你必须在主线程上操做,你就得把它分派到主线程去完成这些操做
当你完成数据读取后,要让ASDK知道你已经完成了,必须调用completeBatchFetching:,而且传YES,这就确保整批提取机制保持同步,下一次提取循环能够正常工做,只有传YES上下文才知道在必要的时候准备下一次更新,若是传NO,什么都不会发生
批量获取demo
- (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView
{
// Decide if the batch fetching mechanism should kick in
if (_stillDataToFetch) {
return YES;
}
return NO;
}
- (void)tableView:(ASTableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context
{
// Fetch data most of the time asynchronoulsy from an API or local database
NSArray *data = ...;
// Insert data into table or collection view
[self insertNewRowsInTableView:newPhotos];
// Decide if it's still necessary to trigger more batch fetches in the future
_stillDataToFetch = ...;
// Properly finish the batch fetch
[context completeBatchFetching:YES]
}
复制代码
查看更多demo能够看
敬请期待......
敬请期待......
这是一个调试功能,把任何的ASControlNodes加上半透明高亮,点击,手势识别,这个范围定义为ASControlNodes的frame+hitTestSlop的范围
在下面的截图中你能够看到
[图不翻译了]
在收到父节点clipsToBounds的剪裁
在你的Appdelegate.m中 添加[ASControlNode setEnableHitTestDebug:YES] 到你的didFinishLaunchingWithOptions: 方法的最上方, 确保在任何ASControllNode初始化前调用这个方法,包括ASButtonNodes, ASImageNodes, and ASTextNodes.
可视化的ASImageNode.image像素缩放 若是像素图像不符合像素大小,这个调试工具会增长了一个红色的文本出如今ASImageNode右下角,
imageSizeInPixels = image.size * image.scale
boundsSizeInPixels = bounds.size * contentsScale
scaleFactor = imageSizeInPixels / boundsSizeInPixels
if (scaleFactor != 1.0) {
NSString *scaleString = [NSString stringWithFormat:@"%.2fx", scaleFactor];
_debugLabelNode.hidden = NO;
}
复制代码
此调试工具在下面的状况很是有用
在下面的截图中,你能够看到,低质量图片被放大所以右下角有文字,你须要优化你的功能,控制最终的尺寸和最佳的图像
[图不翻译了]
在appdelegate.m文件中
导入AsyncDisplayKit+Debug.h
添加[ASImageNode setShouldShowImageScalingOverlay:YES] 到didFinishLaunchingWithOptions: 方法的最上方
先不翻译了吧。。未稳定的功能
先不翻译了把。。未稳定的功能