mirror of
https://github.com/HackPlan/AsyncDisplayKit.git
synced 2026-04-22 19:13:55 +08:00
added basic docs for collectionnode, tablenode, pagernode and asviewcontroller
This commit is contained in:
@@ -2,5 +2,20 @@
|
||||
title: ASCollectionNode
|
||||
layout: docs
|
||||
permalink: /docs/ascollectionnode.html
|
||||
next: astablenode.html
|
||||
---
|
||||
|
||||
ASCollectionNode can be used in place of any UICollectionView. The only requirements are that you replace your
|
||||
|
||||
<code>-cellForRowAtIndexPath:</code>
|
||||
|
||||
method with a
|
||||
|
||||
<code>-nodeForRowAtIndexPath:</code>
|
||||
|
||||
or
|
||||
|
||||
<code>-nodeBlockForRowAtIndexPath:</code>
|
||||
|
||||
Otherwise, a collection node has mostly the same delegate and dataSource methods that a collection view would and is compatible with most UICollectionViewLayouts.
|
||||
|
||||
|
||||
@@ -2,5 +2,45 @@
|
||||
title: ASPagerNode
|
||||
layout: docs
|
||||
permalink: /docs/aspagernode.html
|
||||
next: ascollectionnode.html
|
||||
---
|
||||
|
||||
ASPagerNode is a specialized subclass of ASCollectionNode. Using it allows you to produce a page style UI similar to what you'd create with a UIPageViewController with UIKit. Luckily, the API is quite a bit simpler than UIPageViewController's.
|
||||
|
||||
The main dataSource methods are:
|
||||
|
||||
<code>- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode</code>
|
||||
|
||||
and
|
||||
|
||||
<code>- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index</code>
|
||||
|
||||
or
|
||||
|
||||
<code>- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index</code>
|
||||
|
||||
|
||||
These two methods, just as with ASCollectionNode and ASTableNode need to return either an ASCellNode or an block it can use to return one later.
|
||||
|
||||
One especially useful pattern is to return an ASCellNode that is initialized with an existing UIViewController or ASViewController.
|
||||
|
||||
```
|
||||
- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index
|
||||
{
|
||||
CGSize pagerNodeSize = pagerNode.bounds.size;
|
||||
NSArray *animals = self.animals[index];
|
||||
|
||||
ASCellNode *node = [[ASCellNode alloc] initWithViewControllerBlock:^{
|
||||
return [[AnimalTableNodeController alloc] initWithAnimals:animals];;
|
||||
} didLoadBlock:nil];
|
||||
|
||||
node.preferredFrameSize = pagerNodeSize;
|
||||
|
||||
return node;
|
||||
}
|
||||
```
|
||||
|
||||
In this example, you can see that the node is constructed using the `-initWithViewControllerBlock:` method. It is usually necessary to provide a cell created this way with a preferredFrameSize so that it can be laid out correctly.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,12 +2,34 @@
|
||||
title: ASTableNode
|
||||
layout: docs
|
||||
permalink: /docs/astablenode.html
|
||||
next: display-node.html
|
||||
---
|
||||
|
||||
ASRangeController manages working ranges, but doesn't actually display content. If your content is currently rendered in a UITableView, you can convert it to use ASTableNode and custom nodes — just subclass ASCellNode instead of ASDisplayNode. ASTableNode maintains a UITableView subclass that integrates node-based cells and a working range.
|
||||
ASTableNode is equivalent to UIKit's UITableView.
|
||||
|
||||
ASTableNode doesn't let cells onscreen until their underlying nodes have been sized, and as such can fully benefit from realistic placeholders. Its API is very similar to UITableView (see the Kittens sample project for an example), with some key changes:
|
||||
<div class = "note">
|
||||
If you've used previous versions of ASDK, you'll notice that ASTableView has been removed in favor of ASTableNode. ASTableView (an actual UITableView subclass) is still in use as an internal property of ASTableNode but should no longer be used by itself.
|
||||
|
||||
Instead of implementing -tableView:cellForRowAtIndexPath:, your data source must implement -tableView:nodeForRowAtIndexPath:. This method must be thread-safe and should not implement reuse. Unlike the UITableView version, it won't be called when the row is about to display.
|
||||
That being said, you can still grab a reference to the underlying ASTableView if necessary by accessing the .view property of an ASTableNode.
|
||||
</div>
|
||||
|
||||
ASTableNode can be used in place of any UITableView. The only requirements are that you replace your
|
||||
|
||||
<code>-cellForRowAtIndexPath:</code>
|
||||
|
||||
method with a
|
||||
|
||||
<code>-nodeForRowAtIndexPath:</code>
|
||||
|
||||
or
|
||||
|
||||
<code>-nodeBlockForRowAtIndexPath:</code>
|
||||
|
||||
Otherwise, a table node has mostly the same delegate and dataSource methods that a table view would.
|
||||
|
||||
An important thing to notice is that ASTableNode does not provide a method called:
|
||||
|
||||
<code>-tableNode:HeightForRowAtIndexPath:</code>
|
||||
|
||||
This is because in ASDK, nodes are responsible for determining their height themselves which means you no longer have to write code to determine this detail at the view controller level.
|
||||
|
||||
-tableView:heightForRowAtIndexPath: has been removed — ASTableView lets your cell nodes size themselves. This means you no longer have to manually duplicate or factor out layout and sizing logic for dynamically-sized UITableViewCells!
|
||||
|
||||
@@ -2,5 +2,33 @@
|
||||
title: ASViewController
|
||||
layout: docs
|
||||
permalink: /docs/asviewcontroller.html
|
||||
next: ascellnode.html
|
||||
next: aspagernode.html
|
||||
---
|
||||
|
||||
ASViewController is a direct subclass of UIViewController. For the most part, it can be used in place of any UIViewController relatively easily.
|
||||
|
||||
The main difference is that you construct and return the node you'd like managed as opposed to the way UIViewController provides a view of its own.
|
||||
|
||||
Consider the following ASViewController subclass that would like to use a custom table node as its managed node.
|
||||
|
||||
```
|
||||
- (instancetype)initWithModel:(NSArray *)models
|
||||
{
|
||||
ASTableNode *tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
|
||||
|
||||
if (!(self = [super initWithNode:tableNode])) { return nil; }
|
||||
|
||||
self.models = models;
|
||||
|
||||
self.tableNode = tableNode;
|
||||
self.tableNode.dataSource = self;
|
||||
|
||||
return self;
|
||||
}
|
||||
```
|
||||
|
||||
The most important line is:
|
||||
|
||||
<code>if (!(self = [super initWithNode:tableNode])) { return nil; }</code>
|
||||
|
||||
As you can see, ASViewController's are initialized with a node of your choosing.
|
||||
@@ -5,202 +5,3 @@ permalink: /docs/subclassing.html
|
||||
next: layout-engine.html
|
||||
---
|
||||
|
||||
<article class="post-content">
|
||||
<h2>View hierarchies</h2>
|
||||
|
||||
<p>Sizing and layout of custom view hierarchies are typically done all at once on
|
||||
the main thread. For example, a custom UIView that minimally encloses a text
|
||||
view and an image view might look like this:</p>
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="p">-</span> <span class="p">(</span><span class="bp">CGSize</span><span class="p">)</span><span class="nf">sizeThatFits:</span><span class="p">(</span><span class="bp">CGSize</span><span class="p">)</span><span class="nv">size</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// size the image</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_imageView</span> <span class="nl">sizeThatFits</span><span class="p">:</span><span class="n">size</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// size the text view</span>
|
||||
<span class="bp">CGSize</span> <span class="n">maxTextSize</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_textView</span> <span class="nl">sizeThatFits</span><span class="p">:</span><span class="n">maxTextSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// make sure everything fits</span>
|
||||
<span class="n">CGFloat</span> <span class="n">minHeight</span> <span class="o">=</span> <span class="n">MAX</span><span class="p">(</span><span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">,</span> <span class="n">textSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="k">return</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">minHeight</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">layoutSubviews</span>
|
||||
<span class="p">{</span>
|
||||
<span class="bp">CGSize</span> <span class="n">size</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">;</span> <span class="c1">// convenience</span>
|
||||
|
||||
<span class="c1">// size and layout the image</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_imageView</span> <span class="nl">sizeThatFits</span><span class="p">:</span><span class="n">size</span><span class="p">];</span>
|
||||
<span class="n">_imageView</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>
|
||||
<span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// size and layout the text view</span>
|
||||
<span class="bp">CGSize</span> <span class="n">maxTextSize</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_textView</span> <span class="nl">sizeThatFits</span><span class="p">:</span><span class="n">maxTextSize</span><span class="p">];</span>
|
||||
<span class="n">_textView</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="p">(</span><span class="bp">CGRect</span><span class="p">){</span> <span class="n">CGPointZero</span><span class="p">,</span> <span class="n">textSize</span> <span class="p">};</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
<p>This isn't ideal. We're sizing our subviews twice — once to figure out
|
||||
how big our view needs to be and once when laying it out — and while our
|
||||
layout arithmetic is cheap and quick, we're also blocking the main thread on
|
||||
expensive text sizing.</p>
|
||||
|
||||
<p>We could improve the situation by manually cacheing our subviews' sizes, but
|
||||
that solution comes with its own set of problems. Just adding <code>_imageSize</code> and
|
||||
<code>_textSize</code> ivars wouldn't be enough: for example, if the text were to change,
|
||||
we'd need to recompute its size. The boilerplate would quickly become
|
||||
untenable.</p>
|
||||
|
||||
<p>Further, even with a cache, we'll still be blocking the main thread on sizing
|
||||
<em>sometimes</em>. We could try to shift sizing to a background thread with
|
||||
<code>dispatch_async()</code>, but even if our own code is thread-safe, UIView methods are
|
||||
documented to <a href="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/index.html">only work on the main
|
||||
thread</a>:</p>
|
||||
|
||||
<blockquote>
|
||||
Manipulations to your application’s user interface must occur on the main
|
||||
thread. Thus, you should always call the methods of the UIView class from
|
||||
code running in the main thread of your application. The only time this may
|
||||
not be strictly necessary is when creating the view object itself but all
|
||||
other manipulations should occur on the main thread.
|
||||
</blockquote>
|
||||
|
||||
<p>This is a pretty deep rabbit hole. We could attempt to work around the fact
|
||||
that UILabels and UITextViews cannot safely be sized on background threads by
|
||||
manually creating a TextKit stack and sizing the text ourselves... but that's a
|
||||
laborious duplication of work. Further, if UITextView's layout behaviour
|
||||
changes in an iOS update, our sizing code will break. (And did we mention that
|
||||
TextKit isn't thread-safe either?)</p>
|
||||
|
||||
<h2>Node hierarchies</h2>
|
||||
|
||||
<p>Enter AsyncDisplayKit. Our custom node looks like this:</p>
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="cp">#import <AsyncDisplayKit/AsyncDisplayKit+Subclasses.h></span>
|
||||
|
||||
<span class="p">...</span>
|
||||
|
||||
<span class="c1">// perform expensive sizing operations on a background thread</span>
|
||||
<span class="o">-</span> <span class="p">(</span><span class="bp">CGSize</span><span class="p">)</span><span class="nl">calculateSizeThatFits</span><span class="p">:(</span><span class="bp">CGSize</span><span class="p">)</span><span class="n">constrainedSize</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// size the image</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_imageNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">constrainedSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// size the text node</span>
|
||||
<span class="bp">CGSize</span> <span class="n">maxTextSize</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">constrainedSize</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span>
|
||||
<span class="n">constrainedSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_textNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">maxTextSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// make sure everything fits</span>
|
||||
<span class="n">CGFloat</span> <span class="n">minHeight</span> <span class="o">=</span> <span class="n">MAX</span><span class="p">(</span><span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">,</span> <span class="n">textSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="k">return</span> <span class="nf">CGSizeMake</span><span class="p">(</span><span class="n">constrainedSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">minHeight</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1">// do as little work as possible in main-thread layout</span>
|
||||
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">layout</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// layout the image using its cached size</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="n">_imageNode</span><span class="p">.</span><span class="n">calculatedSize</span><span class="p">;</span>
|
||||
<span class="n">_imageNode</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="nb">self</span><span class="p">.</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>
|
||||
<span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// layout the text view using its cached size</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="n">_textNode</span><span class="p">.</span><span class="n">calculatedSize</span><span class="p">;</span>
|
||||
<span class="n">_textNode</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="p">(</span><span class="bp">CGRect</span><span class="p">){</span> <span class="n">CGPointZero</span><span class="p">,</span> <span class="n">textSize</span> <span class="p">};</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
<p>ASImageNode and ASTextNode, like the rest of AsyncDisplayKit, are thread-safe,
|
||||
so we can size them on background threads. The <code>-measure:</code> method is like
|
||||
<code>-sizeThatFits:</code>, but with side effects: it caches both the argument
|
||||
(<code>constrainedSizeForCalculatedSize</code>) and the result (<code>calculatedSize</code>) for
|
||||
quick access later on — like in our now-snappy <code>-layout</code> implementation.</p>
|
||||
|
||||
<p>As you can see, node hierarchies are sized and laid out in much the same way as
|
||||
their view counterparts. Custom nodes do need to be written with a few things
|
||||
in mind:</p>
|
||||
|
||||
<ul>
|
||||
<li><p>Nodes must recursively measure all of their subnodes in their
|
||||
<code>-calculateSizeThatFits:</code> implementations. Note that the <code>-measure:</code>
|
||||
machinery will only call <code>-calculateSizeThatFits:</code> if a new measurement pass
|
||||
is needed (e.g., if the constrained size has changed).</p></li>
|
||||
<li><p>Nodes should perform any other expensive pre-layout calculations in
|
||||
<code>-calculateSizeThatFits:</code>, cacheing useful intermediate results in ivars as
|
||||
appropriate.</p></li>
|
||||
<li><p>Nodes should call <code>[self invalidateCalculatedSize]</code> when necessary. For
|
||||
example, ASTextNode invalidates its calculated size when its
|
||||
<code>attributedString</code> property is changed.</p></li>
|
||||
</ul>
|
||||
|
||||
<p>For more examples of custom sizing and layout, along with a demo of
|
||||
ASTextNode's features, check out <code>BlurbNode</code> and <code>KittenNode</code> in the
|
||||
<a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens">Kittens</a>
|
||||
sample project.</p>
|
||||
|
||||
<h2>Custom drawing</h2>
|
||||
|
||||
<p>To guarantee thread safety in its highly-concurrent drawing system, the node
|
||||
drawing API diverges substantially from UIView's. Instead of implementing
|
||||
<code>-drawRect:</code>, you must:</p>
|
||||
|
||||
<ol>
|
||||
<li><p>Define an internal "draw parameters" class for your custom node. This
|
||||
class should be able to store any state your node needs to draw itself
|
||||
— it can be a plain old NSObject or even a dictionary.</p></li>
|
||||
<li><p>Return a configured instance of your draw parameters class in
|
||||
<code>-drawParametersForAsyncLayer:</code>. This method will always be called on the
|
||||
main thread.</p></li>
|
||||
<li><p>Implement either <code>+drawRect:withParameters:isCancelled:isRasterizing:</code> or
|
||||
<code>+displayWithParameters:isCancelled:</code>. Note that these are <em>class</em> methods
|
||||
that will not have access to your node's state — only the draw
|
||||
parameters object. They can be called on any thread and must be
|
||||
thread-safe.</p></li>
|
||||
</ol>
|
||||
|
||||
<p>For example, this node will draw a rainbow:</p>
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="k">@interface</span> <span class="nc">RainbowNode</span> : <span class="nc">ASDisplayNode</span>
|
||||
<span class="k">@end</span>
|
||||
|
||||
<span class="k">@implementation</span> <span class="nc">RainbowNode</span>
|
||||
|
||||
<span class="p">+</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">drawRect:</span><span class="p">(</span><span class="bp">CGRect</span><span class="p">)</span><span class="nv">bounds</span>
|
||||
<span class="nf">withParameters:</span><span class="p">(</span><span class="kt">id</span><span class="o"><</span><span class="bp">NSObject</span><span class="o">></span><span class="p">)</span><span class="nv">parameters</span>
|
||||
<span class="nf">isCancelled:</span><span class="p">(</span><span class="kt">asdisplaynode_iscancelled_block_t</span><span class="p">)</span><span class="nv">isCancelledBlock</span>
|
||||
<span class="nf">isRasterizing:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">isRasterizing</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// clear the backing store, but only if we're not rasterising into another layer</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isRasterizing</span><span class="p">)</span> <span class="p">{</span>
|
||||
<span class="p">[[</span><span class="bp">UIColor</span> <span class="n">whiteColor</span><span class="p">]</span> <span class="n">set</span><span class="p">];</span>
|
||||
<span class="n">UIRectFill</span><span class="p">(</span><span class="n">bounds</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1">// UIColor sadly lacks +indigoColor and +violetColor methods</span>
|
||||
<span class="bp">NSArray</span> <span class="o">*</span><span class="n">colors</span> <span class="o">=</span> <span class="l">@[</span> <span class="p">[</span><span class="bp">UIColor</span> <span class="n">redColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">orangeColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">yellowColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">greenColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">blueColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">purpleColor</span><span class="p">]</span> <span class="l">]</span><span class="p">;</span>
|
||||
<span class="n">CGFloat</span> <span class="n">stripeHeight</span> <span class="o">=</span> <span class="n">roundf</span><span class="p">(</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span> <span class="o">/</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">colors</span><span class="p">.</span><span class="n">count</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// draw the stripes</span>
|
||||
<span class="k">for</span> <span class="p">(</span><span class="bp">UIColor</span> <span class="o">*</span><span class="n">color</span> <span class="k">in</span> <span class="n">colors</span><span class="p">)</span> <span class="p">{</span>
|
||||
<span class="bp">CGRect</span> <span class="n">stripe</span> <span class="o">=</span> <span class="n">CGRectZero</span><span class="p">;</span>
|
||||
<span class="n">CGRectDivide</span><span class="p">(</span><span class="n">bounds</span><span class="p">,</span> <span class="o">&</span><span class="n">stripe</span><span class="p">,</span> <span class="o">&</span><span class="n">bounds</span><span class="p">,</span> <span class="n">stripeHeight</span><span class="p">,</span> <span class="n">CGRectMinYEdge</span><span class="p">);</span>
|
||||
<span class="p">[</span><span class="n">color</span> <span class="n">set</span><span class="p">];</span>
|
||||
<span class="n">UIRectFill</span><span class="p">(</span><span class="n">stripe</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="k">@end</span>
|
||||
</code></pre></div>
|
||||
<p>This could easily be extended to support vertical rainbows too, by adding a
|
||||
<code>vertical</code> property to the node, exporting it in
|
||||
<code>-drawParametersForAsyncLayer:</code>, and referencing it in
|
||||
<code>+drawRect:withParameters:isCancelled:isRasterizing:</code>. More-complex nodes can
|
||||
be supported in much the same way.</p>
|
||||
|
||||
<p>For more on custom nodes, check out the <a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASDisplayNode%2BSubclasses.h">subclassing
|
||||
header</a>
|
||||
or read on!</p>
|
||||
|
||||
</article>
|
||||
@@ -4476,11 +4476,26 @@
|
||||
</h1>
|
||||
<p></p>
|
||||
|
||||
|
||||
<p>ASCollectionNode can be used in place of any UICollectionView. The only requirements are that you replace your </p>
|
||||
|
||||
<p><code>-cellForRowAtIndexPath:</code> </p>
|
||||
|
||||
<p>method with a </p>
|
||||
|
||||
<p><code>-nodeForRowAtIndexPath:</code> </p>
|
||||
|
||||
<p>or</p>
|
||||
|
||||
<p><code>-nodeBlockForRowAtIndexPath:</code></p>
|
||||
|
||||
<p>Otherwise, a collection node has mostly the same delegate and dataSource methods that a collection view would and is compatible with most UICollectionViewLayouts.</p>
|
||||
|
||||
|
||||
<div class="docs-prevnext">
|
||||
|
||||
|
||||
<a class="right" href="/docs/astablenode.html">Next →</a>
|
||||
|
||||
</div>
|
||||
|
||||
<a id="_"></a>
|
||||
|
||||
@@ -4476,11 +4476,45 @@
|
||||
</h1>
|
||||
<p></p>
|
||||
|
||||
|
||||
<p>ASPagerNode is a specialized subclass of ASCollectionNode. Using it allows you to produce a page style UI similar to what you'd create with a UIPageViewController with UIKit. Luckily, the API is quite a bit simpler than UIPageViewController's.</p>
|
||||
|
||||
<p>The main dataSource methods are:</p>
|
||||
|
||||
<p><code>- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode</code></p>
|
||||
|
||||
<p>and </p>
|
||||
|
||||
<p><code>- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index</code></p>
|
||||
|
||||
<p>or</p>
|
||||
|
||||
<p><code>- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index</code></p>
|
||||
|
||||
<p>These two methods, just as with ASCollectionNode and ASTableNode need to return either an ASCellNode or an block it can use to return one later. </p>
|
||||
|
||||
<p>One especially useful pattern is to return an ASCellNode that is initialized with an existing UIViewController or ASViewController.</p>
|
||||
<div class="highlight"><pre><code class="language-text" data-lang="text">- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index
|
||||
{
|
||||
CGSize pagerNodeSize = pagerNode.bounds.size;
|
||||
NSArray *animals = self.animals[index];
|
||||
|
||||
ASCellNode *node = [[ASCellNode alloc] initWithViewControllerBlock:^{
|
||||
return [[AnimalTableNodeController alloc] initWithAnimals:animals];;
|
||||
} didLoadBlock:nil];
|
||||
|
||||
node.preferredFrameSize = pagerNodeSize;
|
||||
|
||||
return node;
|
||||
}
|
||||
</code></pre></div>
|
||||
<p>In this example, you can see that the node is constructed using the <code>-initWithViewControllerBlock:</code> method. It is usually necessary to provide a cell created this way with a preferredFrameSize so that it can be laid out correctly.</p>
|
||||
|
||||
|
||||
<div class="docs-prevnext">
|
||||
|
||||
|
||||
<a class="right" href="/docs/ascollectionnode.html">Next →</a>
|
||||
|
||||
</div>
|
||||
|
||||
<a id="_"></a>
|
||||
|
||||
@@ -4476,18 +4476,40 @@
|
||||
</h1>
|
||||
<p></p>
|
||||
|
||||
<p>ASRangeController manages working ranges, but doesn't actually display content. If your content is currently rendered in a UITableView, you can convert it to use ASTableNode and custom nodes — just subclass ASCellNode instead of ASDisplayNode. ASTableNode maintains a UITableView subclass that integrates node-based cells and a working range.</p>
|
||||
<p>ASTableNode is equivalent to UIKit's UITableView. </p>
|
||||
|
||||
<p>ASTableNode doesn't let cells onscreen until their underlying nodes have been sized, and as such can fully benefit from realistic placeholders. Its API is very similar to UITableView (see the Kittens sample project for an example), with some key changes:</p>
|
||||
<div class = "note">
|
||||
If you've used previous versions of ASDK, you'll notice that ASTableView has been removed in favor of ASTableNode. ASTableView (an actual UITableView subclass) is still in use as an internal property of ASTableNode but should no longer be used by itself.
|
||||
|
||||
<p>Instead of implementing -tableView:cellForRowAtIndexPath:, your data source must implement -tableView:nodeForRowAtIndexPath:. This method must be thread-safe and should not implement reuse. Unlike the UITableView version, it won't be called when the row is about to display.</p>
|
||||
That being said, you can still grab a reference to the underlying ASTableView if necessary by accessing the .view property of an ASTableNode.
|
||||
</div>
|
||||
|
||||
<p>-tableView:heightForRowAtIndexPath: has been removed — ASTableView lets your cell nodes size themselves. This means you no longer have to manually duplicate or factor out layout and sizing logic for dynamically-sized UITableViewCells!</p>
|
||||
<p>ASTableNode can be used in place of any UITableView. The only requirements are that you replace your </p>
|
||||
|
||||
<p><code>-cellForRowAtIndexPath:</code> </p>
|
||||
|
||||
<p>method with a </p>
|
||||
|
||||
<p><code>-nodeForRowAtIndexPath:</code> </p>
|
||||
|
||||
<p>or</p>
|
||||
|
||||
<p><code>-nodeBlockForRowAtIndexPath:</code></p>
|
||||
|
||||
<p>Otherwise, a table node has mostly the same delegate and dataSource methods that a table view would.</p>
|
||||
|
||||
<p>An important thing to notice is that ASTableNode does not provide a method called:</p>
|
||||
|
||||
<p><code>-tableNode:HeightForRowAtIndexPath:</code></p>
|
||||
|
||||
<p>This is because in ASDK, nodes are responsible for determining their height themselves which means you no longer have to write code to determine this detail at the view controller level.</p>
|
||||
|
||||
|
||||
<div class="docs-prevnext">
|
||||
|
||||
|
||||
<a class="right" href="/docs/display-node.html">Next →</a>
|
||||
|
||||
</div>
|
||||
|
||||
<a id="_"></a>
|
||||
|
||||
@@ -4476,12 +4476,36 @@
|
||||
</h1>
|
||||
<p></p>
|
||||
|
||||
|
||||
<p>ASViewController is a direct subclass of UIViewController. For the most part, it can be used in place of any UIViewController relatively easily. </p>
|
||||
|
||||
<p>The main difference is that you construct and return the node you'd like managed as opposed to the way UIViewController provides a view of its own.</p>
|
||||
|
||||
<p>Consider the following ASViewController subclass that would like to use a custom table node as its managed node.</p>
|
||||
<div class="highlight"><pre><code class="language-text" data-lang="text">- (instancetype)initWithModel:(NSArray *)models
|
||||
{
|
||||
ASTableNode *tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain];
|
||||
|
||||
if (!(self = [super initWithNode:tableNode])) { return nil; }
|
||||
|
||||
self.models = models;
|
||||
|
||||
self.tableNode = tableNode;
|
||||
self.tableNode.dataSource = self;
|
||||
|
||||
return self;
|
||||
}
|
||||
</code></pre></div>
|
||||
<p>The most important line is:</p>
|
||||
|
||||
<p><code>if (!(self = [super initWithNode:tableNode])) { return nil; }</code></p>
|
||||
|
||||
<p>As you can see, ASViewController's are initialized with a node of your choosing. </p>
|
||||
|
||||
|
||||
<div class="docs-prevnext">
|
||||
|
||||
|
||||
<a class="right" href="/docs/ascellnode.html">Next →</a>
|
||||
<a class="right" href="/docs/aspagernode.html">Next →</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4524,7 +4524,7 @@ rich text support.</li>
|
||||
<li> <strong>ASPagerNode</strong>. A specialized ASCollectionNode which can be used in the same way as a UIPageViewController.</li>
|
||||
</ul>
|
||||
|
||||
<h2><a href = "/docs/layout-engine.html">Layout Engine</a></h2>
|
||||
<h2><a href = "/docs/layout-enginec.html">Layout Engine</a></h2>
|
||||
|
||||
<p>AsyncDisplayKit's layout engine is both one of its most powerful and one of its most unique features. Based on the CSS FlexBox model, it provides a declarative way of specifying a custom node's size and layout of its subnodes. While all nodes are concurrently rendered by default, asynchronous measurement and layout are performed by providing an ASLayoutSpec for each node.</p>
|
||||
|
||||
|
||||
@@ -4476,211 +4476,7 @@
|
||||
</h1>
|
||||
<p></p>
|
||||
|
||||
<p><article class="post-content">
|
||||
<h2>View hierarchies</h2></p>
|
||||
|
||||
<p>Sizing and layout of custom view hierarchies are typically done all at once on
|
||||
the main thread. For example, a custom UIView that minimally encloses a text
|
||||
view and an image view might look like this:</p>
|
||||
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="p">-</span> <span class="p">(</span><span class="bp">CGSize</span><span class="p">)</span><span class="nf">sizeThatFits:</span><span class="p">(</span><span class="bp">CGSize</span><span class="p">)</span><span class="nv">size</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// size the image</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_imageView</span> <span class="nl">sizeThatFits</span><span class="p">:</span><span class="n">size</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// size the text view</span>
|
||||
<span class="bp">CGSize</span> <span class="n">maxTextSize</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_textView</span> <span class="nl">sizeThatFits</span><span class="p">:</span><span class="n">maxTextSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// make sure everything fits</span>
|
||||
<span class="n">CGFloat</span> <span class="n">minHeight</span> <span class="o">=</span> <span class="n">MAX</span><span class="p">(</span><span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">,</span> <span class="n">textSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="k">return</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">minHeight</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">layoutSubviews</span>
|
||||
<span class="p">{</span>
|
||||
<span class="bp">CGSize</span> <span class="n">size</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">;</span> <span class="c1">// convenience</span>
|
||||
|
||||
<span class="c1">// size and layout the image</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_imageView</span> <span class="nl">sizeThatFits</span><span class="p">:</span><span class="n">size</span><span class="p">];</span>
|
||||
<span class="n">_imageView</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>
|
||||
<span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// size and layout the text view</span>
|
||||
<span class="bp">CGSize</span> <span class="n">maxTextSize</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_textView</span> <span class="nl">sizeThatFits</span><span class="p">:</span><span class="n">maxTextSize</span><span class="p">];</span>
|
||||
<span class="n">_textView</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="p">(</span><span class="bp">CGRect</span><span class="p">){</span> <span class="n">CGPointZero</span><span class="p">,</span> <span class="n">textSize</span> <span class="p">};</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p>This isn't ideal. We're sizing our subviews twice — once to figure out
|
||||
how big our view needs to be and once when laying it out — and while our
|
||||
layout arithmetic is cheap and quick, we're also blocking the main thread on
|
||||
expensive text sizing.</p>
|
||||
|
||||
<p>We could improve the situation by manually cacheing our subviews' sizes, but
|
||||
that solution comes with its own set of problems. Just adding <code>_imageSize</code> and
|
||||
<code>_textSize</code> ivars wouldn't be enough: for example, if the text were to change,
|
||||
we'd need to recompute its size. The boilerplate would quickly become
|
||||
untenable.</p>
|
||||
|
||||
<p>Further, even with a cache, we'll still be blocking the main thread on sizing
|
||||
<em>sometimes</em>. We could try to shift sizing to a background thread with
|
||||
<code>dispatch_async()</code>, but even if our own code is thread-safe, UIView methods are
|
||||
documented to <a href="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/index.html">only work on the main
|
||||
thread</a>:</p>
|
||||
|
||||
<blockquote>
|
||||
Manipulations to your application’s user interface must occur on the main
|
||||
thread. Thus, you should always call the methods of the UIView class from
|
||||
code running in the main thread of your application. The only time this may
|
||||
not be strictly necessary is when creating the view object itself but all
|
||||
other manipulations should occur on the main thread.
|
||||
</blockquote>
|
||||
|
||||
<p>This is a pretty deep rabbit hole. We could attempt to work around the fact
|
||||
that UILabels and UITextViews cannot safely be sized on background threads by
|
||||
manually creating a TextKit stack and sizing the text ourselves... but that's a
|
||||
laborious duplication of work. Further, if UITextView's layout behaviour
|
||||
changes in an iOS update, our sizing code will break. (And did we mention that
|
||||
TextKit isn't thread-safe either?)</p>
|
||||
|
||||
<h2>Node hierarchies</h2>
|
||||
|
||||
<p>Enter AsyncDisplayKit. Our custom node looks like this:</p>
|
||||
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="cp">#import <AsyncDisplayKit/AsyncDisplayKit+Subclasses.h></span>
|
||||
|
||||
<span class="p">...</span>
|
||||
|
||||
<span class="c1">// perform expensive sizing operations on a background thread</span>
|
||||
<span class="o">-</span> <span class="p">(</span><span class="bp">CGSize</span><span class="p">)</span><span class="nl">calculateSizeThatFits</span><span class="p">:(</span><span class="bp">CGSize</span><span class="p">)</span><span class="n">constrainedSize</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// size the image</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_imageNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">constrainedSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// size the text node</span>
|
||||
<span class="bp">CGSize</span> <span class="n">maxTextSize</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">constrainedSize</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span>
|
||||
<span class="n">constrainedSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_textNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">maxTextSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// make sure everything fits</span>
|
||||
<span class="n">CGFloat</span> <span class="n">minHeight</span> <span class="o">=</span> <span class="n">MAX</span><span class="p">(</span><span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">,</span> <span class="n">textSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="k">return</span> <span class="nf">CGSizeMake</span><span class="p">(</span><span class="n">constrainedSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">minHeight</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1">// do as little work as possible in main-thread layout</span>
|
||||
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">layout</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// layout the image using its cached size</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="n">_imageNode</span><span class="p">.</span><span class="n">calculatedSize</span><span class="p">;</span>
|
||||
<span class="n">_imageNode</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="nb">self</span><span class="p">.</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>
|
||||
<span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// layout the text view using its cached size</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="n">_textNode</span><span class="p">.</span><span class="n">calculatedSize</span><span class="p">;</span>
|
||||
<span class="n">_textNode</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="p">(</span><span class="bp">CGRect</span><span class="p">){</span> <span class="n">CGPointZero</span><span class="p">,</span> <span class="n">textSize</span> <span class="p">};</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p>ASImageNode and ASTextNode, like the rest of AsyncDisplayKit, are thread-safe,
|
||||
so we can size them on background threads. The <code>-measure:</code> method is like
|
||||
<code>-sizeThatFits:</code>, but with side effects: it caches both the argument
|
||||
(<code>constrainedSizeForCalculatedSize</code>) and the result (<code>calculatedSize</code>) for
|
||||
quick access later on — like in our now-snappy <code>-layout</code> implementation.</p>
|
||||
|
||||
<p>As you can see, node hierarchies are sized and laid out in much the same way as
|
||||
their view counterparts. Custom nodes do need to be written with a few things
|
||||
in mind:</p>
|
||||
|
||||
<ul>
|
||||
<li><p>Nodes must recursively measure all of their subnodes in their
|
||||
<code>-calculateSizeThatFits:</code> implementations. Note that the <code>-measure:</code>
|
||||
machinery will only call <code>-calculateSizeThatFits:</code> if a new measurement pass
|
||||
is needed (e.g., if the constrained size has changed).</p></li>
|
||||
<li><p>Nodes should perform any other expensive pre-layout calculations in
|
||||
<code>-calculateSizeThatFits:</code>, cacheing useful intermediate results in ivars as
|
||||
appropriate.</p></li>
|
||||
<li><p>Nodes should call <code>[self invalidateCalculatedSize]</code> when necessary. For
|
||||
example, ASTextNode invalidates its calculated size when its
|
||||
<code>attributedString</code> property is changed.</p></li>
|
||||
</ul>
|
||||
|
||||
<p>For more examples of custom sizing and layout, along with a demo of
|
||||
ASTextNode's features, check out <code>BlurbNode</code> and <code>KittenNode</code> in the
|
||||
<a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens">Kittens</a>
|
||||
sample project.</p>
|
||||
|
||||
<h2>Custom drawing</h2>
|
||||
|
||||
<p>To guarantee thread safety in its highly-concurrent drawing system, the node
|
||||
drawing API diverges substantially from UIView's. Instead of implementing
|
||||
<code>-drawRect:</code>, you must:</p>
|
||||
|
||||
<ol>
|
||||
<li><p>Define an internal "draw parameters" class for your custom node. This
|
||||
class should be able to store any state your node needs to draw itself
|
||||
— it can be a plain old NSObject or even a dictionary.</p></li>
|
||||
<li><p>Return a configured instance of your draw parameters class in
|
||||
<code>-drawParametersForAsyncLayer:</code>. This method will always be called on the
|
||||
main thread.</p></li>
|
||||
<li><p>Implement either <code>+drawRect:withParameters:isCancelled:isRasterizing:</code> or
|
||||
<code>+displayWithParameters:isCancelled:</code>. Note that these are <em>class</em> methods
|
||||
that will not have access to your node's state — only the draw
|
||||
parameters object. They can be called on any thread and must be
|
||||
thread-safe.</p></li>
|
||||
</ol>
|
||||
|
||||
<p>For example, this node will draw a rainbow:</p>
|
||||
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="k">@interface</span> <span class="nc">RainbowNode</span> : <span class="nc">ASDisplayNode</span>
|
||||
<span class="k">@end</span>
|
||||
|
||||
<span class="k">@implementation</span> <span class="nc">RainbowNode</span>
|
||||
|
||||
<span class="p">+</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">drawRect:</span><span class="p">(</span><span class="bp">CGRect</span><span class="p">)</span><span class="nv">bounds</span>
|
||||
<span class="nf">withParameters:</span><span class="p">(</span><span class="kt">id</span><span class="o"><</span><span class="bp">NSObject</span><span class="o">></span><span class="p">)</span><span class="nv">parameters</span>
|
||||
<span class="nf">isCancelled:</span><span class="p">(</span><span class="kt">asdisplaynode_iscancelled_block_t</span><span class="p">)</span><span class="nv">isCancelledBlock</span>
|
||||
<span class="nf">isRasterizing:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">isRasterizing</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// clear the backing store, but only if we're not rasterising into another layer</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isRasterizing</span><span class="p">)</span> <span class="p">{</span>
|
||||
<span class="p">[[</span><span class="bp">UIColor</span> <span class="n">whiteColor</span><span class="p">]</span> <span class="n">set</span><span class="p">];</span>
|
||||
<span class="n">UIRectFill</span><span class="p">(</span><span class="n">bounds</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1">// UIColor sadly lacks +indigoColor and +violetColor methods</span>
|
||||
<span class="bp">NSArray</span> <span class="o">*</span><span class="n">colors</span> <span class="o">=</span> <span class="l">@[</span> <span class="p">[</span><span class="bp">UIColor</span> <span class="n">redColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">orangeColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">yellowColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">greenColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">blueColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">purpleColor</span><span class="p">]</span> <span class="l">]</span><span class="p">;</span>
|
||||
<span class="n">CGFloat</span> <span class="n">stripeHeight</span> <span class="o">=</span> <span class="n">roundf</span><span class="p">(</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span> <span class="o">/</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">colors</span><span class="p">.</span><span class="n">count</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// draw the stripes</span>
|
||||
<span class="k">for</span> <span class="p">(</span><span class="bp">UIColor</span> <span class="o">*</span><span class="n">color</span> <span class="k">in</span> <span class="n">colors</span><span class="p">)</span> <span class="p">{</span>
|
||||
<span class="bp">CGRect</span> <span class="n">stripe</span> <span class="o">=</span> <span class="n">CGRectZero</span><span class="p">;</span>
|
||||
<span class="n">CGRectDivide</span><span class="p">(</span><span class="n">bounds</span><span class="p">,</span> <span class="o">&</span><span class="n">stripe</span><span class="p">,</span> <span class="o">&</span><span class="n">bounds</span><span class="p">,</span> <span class="n">stripeHeight</span><span class="p">,</span> <span class="n">CGRectMinYEdge</span><span class="p">);</span>
|
||||
<span class="p">[</span><span class="n">color</span> <span class="n">set</span><span class="p">];</span>
|
||||
<span class="n">UIRectFill</span><span class="p">(</span><span class="n">stripe</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="k">@end</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p>This could easily be extended to support vertical rainbows too, by adding a
|
||||
<code>vertical</code> property to the node, exporting it in
|
||||
<code>-drawParametersForAsyncLayer:</code>, and referencing it in
|
||||
<code>+drawRect:withParameters:isCancelled:isRasterizing:</code>. More-complex nodes can
|
||||
be supported in much the same way.</p>
|
||||
|
||||
<p>For more on custom nodes, check out the <a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASDisplayNode%2BSubclasses.h">subclassing
|
||||
header</a>
|
||||
or read on!</p>
|
||||
<div class="highlight"><pre><code class="language-text" data-lang="text"></article>
|
||||
</code></pre></div>
|
||||
|
||||
|
||||
<div class="docs-prevnext">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user