遷移老文章到掘金 文檔比較老了,不適用最新的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: 方法的最上方
先不翻譯了吧。。未穩定的功能
先不翻譯了把。。未穩定的功能