last removal

This commit is contained in:
Luke Parham
2016-02-09 00:30:58 -06:00
parent 5064445fd4
commit fcaa24688d
10 changed files with 0 additions and 2360 deletions

View File

@@ -1,307 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Custom nodes &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/2/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Custom nodes &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/2/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Custom nodes
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/2-custom-nodes.md" target="_blank">[edit]</a>
</h1>
</header>
<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&#39;t ideal. We&#39;re sizing our subviews twice &mdash; once to figure out
how big our view needs to be and once when laying it out &mdash; and while our
layout arithmetic is cheap and quick, we&#39;re also blocking the main thread on
expensive text sizing.</p>
<p>We could improve the situation by manually cacheing our subviews&#39; sizes, but
that solution comes with its own set of problems. Just adding <code>_imageSize</code> and
<code>_textSize</code> ivars wouldn&#39;t be enough: for example, if the text were to change,
we&#39;d need to recompute its size. The boilerplate would quickly become
untenable.</p>
<p>Further, even with a cache, we&#39;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>
<p>Manipulations to your applications 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.</p>
</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&#39;s a
laborious duplication of work. Further, if UITextView&#39;s layout behaviour
changes in an iOS update, our sizing code will break. (And did we mention that
TextKit isn&#39;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 &lt;AsyncDisplayKit/AsyncDisplayKit+Subclasses.h&gt;</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 &mdash; 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&#39;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&#39;s. Instead of implementing
<code>-drawRect:</code>, you must:</p>
<ol>
<li><p>Define an internal &quot;draw parameters&quot; class for your custom node. This
class should be able to store any state your node needs to draw itself
&mdash; 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&#39;s state &mdash; 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">&lt;</span><span class="bp">NSObject</span><span class="o">&gt;</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&#39;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">&amp;</span><span class="n">stripe</span><span class="p">,</span> <span class="o">&amp;</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>
<div class="docs-prevnext">
<a class="docs-prev" href="/guide/">&larr; prev</a>
<a class="docs-next" href="/guide/3/">next &rarr;</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -1,202 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Asynchronous display &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/3/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Asynchronous display &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/3/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Asynchronous display
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/3-asynchronous-display.md" target="_blank">[edit]</a>
</h1>
</header>
<article class="post-content">
<h2>Realistic placeholders</h2>
<p>Nodes need to complete both a <em>measurement pass</em> and a <em>display pass</em> before
they&#39;re fully rendered. It&#39;s possible to force either step to happen
synchronously: call <code>-measure:</code> in <code>-layoutSubviews</code> to perform sizing on the
main thread, or set a node&#39;s <code>displaysAsynchronously</code> flag to NO to disable
ASDK&#39;s async display machinery. (AsyncDisplayKit can still improve your app&#39;s
performance even when rendering fully synchronously &mdash; more on that
later!)</p>
<p>The recommended way to use ASDK is to only add nodes to your view hierarchy
once they&#39;ve been sized. This avoids unsightly layout changes as the
measurement pass completes, but if you enable asynchronous display, it will
always be possible for a node to appear onscreen before its content has fully
rendered. We&#39;ll discuss techniques to minimise this shortly, but you should
take it into account and include <em>realistic placeholders</em> in your app designs.</p>
<p>Once its measurement pass has completed, a node can accurately place all of its
subnodes onscreen &mdash; they&#39;ll just be blank. The easiest way to make a
realistic placeholder is to set static background colours on your subnodes.
This effect looks better than generic placeholder images because it varies
based on the content being loaded, and it works particularly well for opaque
images. You can also create visually-appealing placeholder nodes, like the
shimmery lines representing text in Paper as its stories are loaded, and swap
them out with your content nodes once they&#39;ve finished displaying.</p>
<h2>Working range</h2>
<p>So far, we&#39;ve only discussed asynchronous sizing: toss a &quot;create a node
hierarchy and measure it&quot; block onto a background thread, then trampoline to
the main thread to add it to the view hierarchy when that&#39;s done. Ideally, as
much content as possible should be fully-rendered and ready to go as soon as
the user scrolls to it. This requires triggering display passes in advance.</p>
<p>If your app&#39;s content is in a scroll view or can be paged through, like
Instagram&#39;s main feed or Paper&#39;s story strip, the solution is a <em>working
range</em>. A working range controller tracks the <em>visible range</em>, the subset of
content that&#39;s currently visible onscreen, and enqueues asynchronous rendering
for the next few screenfuls of content. As the user scrolls, a screenful or
two of previous content is preserved; the rest is cleared to conserve memory.
If she starts scrolling in the other direction, the working range trashes its
render queue and starts pre-rendering in the new direction of scroll &mdash;
and because of the buffer of previous content, this entire process will
typically be invisible.</p>
<p>AsyncDisplayKit includes a generic working range controller,
<code>ASRangeController</code>. Its working range size can be tuned depending on your
app: if your nodes are simple, even an iPhone 4 can maintain a substantial
working range, but heavyweight nodes like Facebook stories are expensive and
need to be pruned quickly.</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="n">ASRangeController</span> <span class="o">*</span><span class="n">rangeController</span> <span class="o">=</span> <span class="p">[[</span><span class="n">ASRangeController</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">rangeController</span><span class="p">.</span><span class="n">tuningParameters</span> <span class="o">=</span> <span class="p">(</span><span class="n">ASRangeTuningParameters</span><span class="p">){</span>
<span class="p">.</span><span class="n">leadingBufferScreenfuls</span> <span class="o">=</span> <span class="mf">2.0f</span><span class="p">;</span> <span class="c1">// two screenfuls in the direction of scroll</span>
<span class="p">.</span><span class="n">trailingBufferScreenfuls</span> <span class="o">=</span> <span class="mf">0.5f</span><span class="p">;</span> <span class="c1">// one-half screenful in the other direction</span>
<span class="p">};</span>
</code></pre></div>
<p>If you use a working range, you should profile your app and consider tuning it
differently on a per-device basis. iPhone 4 has 512MB of RAM and a single-core
A4 chipset, while iPhone 6 has 1GB of RAM and the orders-of-magnitude-faster
multicore A8 &mdash; and if your app supports iOS 7, it will be used on both.</p>
<h2>ASTableView</h2>
<p>ASRangeController manages working ranges, but doesn&#39;t actually display content.
If your content is currently rendered in a UITableView, you can convert it to
use <code>ASTableView</code> and custom nodes &mdash; just subclass <code>ASCellNode</code> instead
of ASDisplayNode. ASTableView is a UITableView subclass that integrates
node-based cells and a working range.</p>
<p>ASTableView doesn&#39;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
<a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens">Kittens</a>
sample project for an example), with some key changes:</p>
<ul>
<li><p>Rather than setting the table view&#39;s <code>.delegate</code> and <code>.dataSource</code>, you set
its <code>.asyncDelegate</code> and <code>.asyncDataSource</code>. See
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASTableView.h">ASTableView.h</a>
for how its delegate and data source protocols differ from UITableView&#39;s.</p></li>
<li><p>Instead of implementing <code>-tableView:cellForRowAtIndexPath:</code>, your data
source must implement <code>-tableView:nodeForRowAtIndexPath:</code>. This method must
be thread-safe and should not implement reuse. Unlike the UITableView
version, it won&#39;t be called when the row is about to display.</p></li>
<li><p><code>-tableView:heightForRowAtIndexPath:</code> has been removed &mdash; 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></li>
</ul>
<p>Next up, how to get the most out of ASDK in your app.</p>
</article>
<div class="docs-prevnext">
<a class="docs-prev" href="/guide/2/">&larr; prev</a>
<a class="docs-next" href="/guide/4/">next &rarr;</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -1,242 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Making the most of AsyncDisplayKit &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/4/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Making the most of AsyncDisplayKit &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/4/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Making the most of AsyncDisplayKit
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/4-making-the-most-of-asdk.md" target="_blank">[edit]</a>
</h1>
</header>
<article class="post-content">
<h2>A note on optimisation</h2>
<p>AsyncDisplayKit is powerful and flexible, but it is not a panacea. If your app
has a complex image- or text-heavy user interface, ASDK can definitely help
improve its performance &mdash; but if you&#39;re blocking the main thread on
network requests, you should consider rearchitecting a few things first. :]</p>
<p>So why is it worthwhile to change the way we do view layout and rendering,
given that UIKit has always been locked to the main thread and performant iOS
apps have been shipping since iPhone&#39;s launch?</p>
<h3>Modern animations</h3>
<p>Until iOS 7, static animations (à la <code>+[UIView
animateWithDuration:animations:]</code>) were the standard. The post-skeuomorphism
redesign brought with it highly-interactive, physics-based animations, with
springs joining the ranks of constant animation functions like
<code>UIViewAnimationOptionCurveEaseInOut</code>.</p>
<p>Classic animations aren&#39;t actually executed in your app. They&#39;re executed
out-of-process, in the high-priority Core Animation render server. Thanks to
pre-emptive multitasking, an app can block its main thread continuously without
causing the animation to drop a single frame.</p>
<p>Critically, dynamic animations can&#39;t be offloaded the same way, and both
<a href="https://github.com/facebook/pop">pop</a> and UIKit Dynamics execute physics
simulations on your app&#39;s main thread. This is because executing arbitrary
code in the render server would introduce unacceptable latency, even if it
could be done securely.</p>
<p>Physics-based animations are often interactive, letting you start an animation
and interrupt it before it completes. Paper lets you fling objects across the
screen and catch them before they land, or grab a view that&#39;s being pulled by a
spring and tear it off. This requires processing touch events and updating
animation targets in realtime &mdash; even short delays for inter-process
communication would shatter the illusion.</p>
<p>(Fun fact: Inertial scrolling is also a physics animation! UIScrollView has
always implemented its animations on the main thread, which is why stuttery
scrolling is the hallmark of a slow app.)</p>
<h3>The main-thread bottleneck</h3>
<p>Physics animations aren&#39;t the only thing that need to happen on the main
thread. The main thread&#39;s <a href="https://developer.apple.com/library/ios/documentation/cocoa/conceptual/multithreading/runloopmanagement/runloopmanagement.html">run
loop</a>
is responsible for handling touch events and initiating drawing operations
&mdash; and with UIKit in the mix, it also has to render text, decode images,
and do any other custom drawing (e.g., using Core Graphics).</p>
<p>If an iteration of the main thread&#39;s run loop takes too long, it will drop an
animation frame and may fail to handle touch events in time. Each iteration of
the run loop must complete within 16ms in order to drive 60fps animations, and
your own code typically has less than 10ms to execute. This means that the
best way to keep your app smooth and responsive is to do as little work on the
main thread as possible.</p>
<p>What&#39;s more, the main thread only executes on one core! Single-threaded view
hierarchies can&#39;t take advantage of the multicore CPUs in all modern iOS
devices. This is important for more than just performance reasons &mdash; it&#39;s
also critical for battery life. Running the CPU on all cores for a short time
is preferable to running one core for an extended amount of time: if the
processor can <em>race to sleep</em> by finishing its work and idling faster, it can
spend more time in a low-power mode, improving battery life.</p>
<h2>When to go asynchronous</h2>
<p>AsyncDisplayKit really shines when used fully asynchronously, shifting both
measurement and rendering passes off the main thread and onto multiple cores.
This requires a completely node-based hierarchy. Just as degrading from
UIViews to CALayers disables view-specific functionality like touch handling
from that point on, degrading from nodes to views disables async behaviour.</p>
<p>You don&#39;t, however, need to convert your app&#39;s entire view hierarchy to nodes.
In fact, you shouldn&#39;t! Asynchronously bringing up your app&#39;s core UI, like
navigation elements or tab bars, would be a very confusing experience. Those
elements of your apps can still be nodes, but should be fully synchronous to
guarantee a fully-usable interface as quickly as possible. (This is why
UIWindow has no node equivalent.)</p>
<p>There are two key situations where asynchronous hierarchies excel:</p>
<ol>
<li><p><em>Parallelisation</em>. Measuring and rendering UITableViewCells (or your app&#39;s
equivalent, e.g., story cards in Paper) are embarrassingly parallel
problems. Table cells typically have a fixed width and variable height
determined by their contents &mdash; the argument to <code>-measure:</code> for one
cell doesn&#39;t depend on any other cells&#39; calculations, so we can enqueue an
arbitrary number of cell measurement passes at once.</p></li>
<li><p><em>Preloading</em>. An app with five tabs should synchronously load the first
one so content is ready to go as quickly as possible. Once this is
complete and the CPU is idle, why not asynchronously prepare the other tabs
so the user doesn&#39;t have to wait? This is inconvenient with views, but
very easy with nodes.</p></li>
</ol>
<p>Paper&#39;s asynchronous rendering starts at the story strip. You should profile
your app and watch how people use it in the wild to decide what combination of
synchrony and asynchrony yields the best user experience.</p>
<h2>Additional optimisations</h2>
<p>Complex hierarchies &mdash; even when rendered asynchronously &mdash; can
impose a cost because of the sheer number of views involved. Working around
this can be painful, but AsyncDisplayKit makes it easy!</p>
<ul>
<li><p><em>Layer-backing</em>. In some cases, you can substantially improve your app&#39;s
performance by using layers instead of views. Manually converting
view-based code to layers is laborious due to the difference in APIs.
Worse, if at some point you need to enable touch handling or other
view-specific functionality, you have to manually convert everything back
(and risk regressions!).</p>
<p>With nodes, converting an entire subtree from views to layers is as simple
as...</p>
<div class="highlight"><pre><code class="language-text" data-lang="text">rootNode.layerBacked = YES;
</code></pre></div>
<p>...and if you need to go back, it&#39;s as simple as deleting one line. We
recommend enabling layer-backing as a matter of course in any custom node
that doesn&#39;t need touch handling.</p></li>
<li><p><em>Precompositing</em>. Flattening an entire view hierarchy into a single layer
also improves performance, but comes with a hit to maintainability and
hierarchy-based reasoning. Nodes can do this for you too!</p>
<div class="highlight"><pre><code class="language-text" data-lang="text">rootNode.shouldRasterizeDescendants = YES;
</code></pre></div>
<p>...will cause the entire node hierarchy from that point on to be rendered
into one layer.</p></li>
</ul>
<p>Next up: AsyncDisplayKit, under the hood.</p>
</article>
<div class="docs-prevnext">
<a class="docs-prev" href="/guide/3/">&larr; prev</a>
<a class="docs-next" href="/guide/5/">next &rarr;</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -1,187 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Under the hood &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/5/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Under the hood &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/5/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Under the hood
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/5-under-the-hood.md" target="_blank">[edit]</a>
</h1>
</header>
<article class="post-content">
<h2>Node architecture</h2>
<p><em>(Skip to the next section if you&#39;re not interested in AsyncDisplayKit implementation details.)</em></p>
<p>We&#39;ve described nodes as an abstraction over views and layers, and shown how to
interact with the underlying UIViews and CALayers when necessary. Nodes don&#39;t
wrap or vend their UIKit counterparts, though &mdash; an ASImageNode&#39;s <code>.view</code>
is not a UIImageView! So how do nodes work?</p>
<p><strong>NOTE:</strong> Classes whose names begin with <code>_</code> are private. Don&#39;t use them
directly!</p>
<p>Creating a node doesn&#39;t create its underlying view-layer pair. This is why you
can create nodes cheaply and on background threads. When you use a UIView or
CALayer property on a node, you&#39;re actually interacting with a proxy object,
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Private/_ASPendingState.h"><code>_ASPendingState</code></a>,
that&#39;s preconfigured to match UIView and CALayer defaults.</p>
<p>The first access to a node&#39;s <code>.view</code> or <code>.layer</code> property causes both to be
initialised and configured with the node&#39;s current state. If it has subnodes,
they are recursively loaded as well. Once a node has been loaded, the proxy
object is destroyed and the node becomes main-thread-affined &mdash; its
properties will update the underlying view directly. (Layer-backed nodes do
the same, not loading views.)</p>
<p>Nodes are powered by
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Details/_ASDisplayLayer.h"><code>_ASDisplayLayer</code></a>
and
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Details/_ASDisplayView.h"><code>_ASDisplayView</code></a>.
These are lightweight to create and add to their respective hierarchies, and
provide integration points that allow nodes to act as full-fledged views or
layers. It&#39;s possible to create nodes that are backed by custom view or layer
classes, but doing so is strongly discouraged as it disables the majority of
ASDK&#39;s functionality. </p>
<p>When Core Animation asks an <code>_ASDisplayLayer</code> to draw itself, the request is
forwarded to its node. Unless asynchronous display has been disabled, the
actual draw call won&#39;t happen immediately or on the main thread. Instead, a
display block will be added to a background queue. These blocks are executed
in parallel, but you can enable <code>ASDISPLAYNODE_DELAY_DISPLAY</code> in
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Private/ASDisplayNode%2BAsyncDisplay.mm"><code>ASDisplayNode(AsyncDisplay)</code></a>
to serialise the render system for debugging.</p>
<p>Common UIView subclass hooks are forwarded from <code>_ASDisplayView</code> to its
underlying node, including touch handling, hit-testing, and gesture recogniser
delegate calls. Because an <code>_ASDisplayView</code>&#39;s layer is an <code>_ASDisplayLayer</code>,
view-backed nodes also participate in asynchronous display.</p>
<h2>In practice</h2>
<p>What does this mean for your custom nodes?</p>
<p>You can implement methods like <code>-touchesBegan:withEvent:</code> /
<code>touchesMoved:withEvent:</code> / <code>touchesEnded:withEvent:</code> /
<code>touchesCancelled:withEvent:</code> in your nodes exactly as you would in a UIView
subclass. If you find you need a subclass hook that hasn&#39;t already been
provided, please file an issue on GitHub &mdash; or add it yourself and submit a
pull request!</p>
<p>If you need to interact or configure your node&#39;s underlying view or layer,
don&#39;t do so in <code>-init</code>. Instead, override <code>-didLoad</code> and check if you&#39;re
layer-backed:</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">didLoad</span>
<span class="p">{</span>
<span class="p">[</span><span class="nb">super</span> <span class="n">didLoad</span><span class="p">];</span>
<span class="c1">// add a gesture recogniser, if we have a view to add it to</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">self</span><span class="p">.</span><span class="n">layerBacked</span><span class="p">)</span> <span class="p">{</span>
<span class="n">_gestureRecogniser</span> <span class="o">=</span> <span class="p">[[</span><span class="bp">UITapGestureRecognizer</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithTarget</span><span class="p">:</span><span class="nb">self</span>
<span class="nl">action</span><span class="p">:</span><span class="k">@selector</span><span class="p">(</span><span class="nl">_tap</span><span class="p">:)];</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2><em>fin.</em></h2>
<p>Thanks for reading! If you have any questions, please file a GitHub issue or
post in the <a href="https://www.facebook.com/groups/551597518288687">Facebook group</a>.
We&#39;d love to hear from you.</p>
</article>
<div class="docs-prevnext">
<a class="docs-prev" href="/guide/4/">&larr; prev</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -1,307 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Custom nodes &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/2/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Custom nodes &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/2/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Custom nodes
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/2-custom-nodes.md" target="_blank">[edit]</a>
</h1>
</header>
<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&#39;t ideal. We&#39;re sizing our subviews twice &mdash; once to figure out
how big our view needs to be and once when laying it out &mdash; and while our
layout arithmetic is cheap and quick, we&#39;re also blocking the main thread on
expensive text sizing.</p>
<p>We could improve the situation by manually cacheing our subviews&#39; sizes, but
that solution comes with its own set of problems. Just adding <code>_imageSize</code> and
<code>_textSize</code> ivars wouldn&#39;t be enough: for example, if the text were to change,
we&#39;d need to recompute its size. The boilerplate would quickly become
untenable.</p>
<p>Further, even with a cache, we&#39;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>
<p>Manipulations to your applications 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.</p>
</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&#39;s a
laborious duplication of work. Further, if UITextView&#39;s layout behaviour
changes in an iOS update, our sizing code will break. (And did we mention that
TextKit isn&#39;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 &lt;AsyncDisplayKit/AsyncDisplayKit+Subclasses.h&gt;</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 &mdash; 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&#39;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&#39;s. Instead of implementing
<code>-drawRect:</code>, you must:</p>
<ol>
<li><p>Define an internal &quot;draw parameters&quot; class for your custom node. This
class should be able to store any state your node needs to draw itself
&mdash; 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&#39;s state &mdash; 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">&lt;</span><span class="bp">NSObject</span><span class="o">&gt;</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&#39;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">&amp;</span><span class="n">stripe</span><span class="p">,</span> <span class="o">&amp;</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>
<div class="docs-prevnext">
<a class="docs-prev" href="/guide/">&larr; prev</a>
<a class="docs-next" href="/guide/3/">next &rarr;</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -1,202 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Asynchronous display &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/3/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Asynchronous display &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/3/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Asynchronous display
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/3-asynchronous-display.md" target="_blank">[edit]</a>
</h1>
</header>
<article class="post-content">
<h2>Realistic placeholders</h2>
<p>Nodes need to complete both a <em>measurement pass</em> and a <em>display pass</em> before
they&#39;re fully rendered. It&#39;s possible to force either step to happen
synchronously: call <code>-measure:</code> in <code>-layoutSubviews</code> to perform sizing on the
main thread, or set a node&#39;s <code>displaysAsynchronously</code> flag to NO to disable
ASDK&#39;s async display machinery. (AsyncDisplayKit can still improve your app&#39;s
performance even when rendering fully synchronously &mdash; more on that
later!)</p>
<p>The recommended way to use ASDK is to only add nodes to your view hierarchy
once they&#39;ve been sized. This avoids unsightly layout changes as the
measurement pass completes, but if you enable asynchronous display, it will
always be possible for a node to appear onscreen before its content has fully
rendered. We&#39;ll discuss techniques to minimise this shortly, but you should
take it into account and include <em>realistic placeholders</em> in your app designs.</p>
<p>Once its measurement pass has completed, a node can accurately place all of its
subnodes onscreen &mdash; they&#39;ll just be blank. The easiest way to make a
realistic placeholder is to set static background colours on your subnodes.
This effect looks better than generic placeholder images because it varies
based on the content being loaded, and it works particularly well for opaque
images. You can also create visually-appealing placeholder nodes, like the
shimmery lines representing text in Paper as its stories are loaded, and swap
them out with your content nodes once they&#39;ve finished displaying.</p>
<h2>Working range</h2>
<p>So far, we&#39;ve only discussed asynchronous sizing: toss a &quot;create a node
hierarchy and measure it&quot; block onto a background thread, then trampoline to
the main thread to add it to the view hierarchy when that&#39;s done. Ideally, as
much content as possible should be fully-rendered and ready to go as soon as
the user scrolls to it. This requires triggering display passes in advance.</p>
<p>If your app&#39;s content is in a scroll view or can be paged through, like
Instagram&#39;s main feed or Paper&#39;s story strip, the solution is a <em>working
range</em>. A working range controller tracks the <em>visible range</em>, the subset of
content that&#39;s currently visible onscreen, and enqueues asynchronous rendering
for the next few screenfuls of content. As the user scrolls, a screenful or
two of previous content is preserved; the rest is cleared to conserve memory.
If she starts scrolling in the other direction, the working range trashes its
render queue and starts pre-rendering in the new direction of scroll &mdash;
and because of the buffer of previous content, this entire process will
typically be invisible.</p>
<p>AsyncDisplayKit includes a generic working range controller,
<code>ASRangeController</code>. Its working range size can be tuned depending on your
app: if your nodes are simple, even an iPhone 4 can maintain a substantial
working range, but heavyweight nodes like Facebook stories are expensive and
need to be pruned quickly.</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="n">ASRangeController</span> <span class="o">*</span><span class="n">rangeController</span> <span class="o">=</span> <span class="p">[[</span><span class="n">ASRangeController</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">rangeController</span><span class="p">.</span><span class="n">tuningParameters</span> <span class="o">=</span> <span class="p">(</span><span class="n">ASRangeTuningParameters</span><span class="p">){</span>
<span class="p">.</span><span class="n">leadingBufferScreenfuls</span> <span class="o">=</span> <span class="mf">2.0f</span><span class="p">;</span> <span class="c1">// two screenfuls in the direction of scroll</span>
<span class="p">.</span><span class="n">trailingBufferScreenfuls</span> <span class="o">=</span> <span class="mf">0.5f</span><span class="p">;</span> <span class="c1">// one-half screenful in the other direction</span>
<span class="p">};</span>
</code></pre></div>
<p>If you use a working range, you should profile your app and consider tuning it
differently on a per-device basis. iPhone 4 has 512MB of RAM and a single-core
A4 chipset, while iPhone 6 has 1GB of RAM and the orders-of-magnitude-faster
multicore A8 &mdash; and if your app supports iOS 7, it will be used on both.</p>
<h2>ASTableView</h2>
<p>ASRangeController manages working ranges, but doesn&#39;t actually display content.
If your content is currently rendered in a UITableView, you can convert it to
use <code>ASTableView</code> and custom nodes &mdash; just subclass <code>ASCellNode</code> instead
of ASDisplayNode. ASTableView is a UITableView subclass that integrates
node-based cells and a working range.</p>
<p>ASTableView doesn&#39;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
<a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens">Kittens</a>
sample project for an example), with some key changes:</p>
<ul>
<li><p>Rather than setting the table view&#39;s <code>.delegate</code> and <code>.dataSource</code>, you set
its <code>.asyncDelegate</code> and <code>.asyncDataSource</code>. See
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASTableView.h">ASTableView.h</a>
for how its delegate and data source protocols differ from UITableView&#39;s.</p></li>
<li><p>Instead of implementing <code>-tableView:cellForRowAtIndexPath:</code>, your data
source must implement <code>-tableView:nodeForRowAtIndexPath:</code>. This method must
be thread-safe and should not implement reuse. Unlike the UITableView
version, it won&#39;t be called when the row is about to display.</p></li>
<li><p><code>-tableView:heightForRowAtIndexPath:</code> has been removed &mdash; 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></li>
</ul>
<p>Next up, how to get the most out of ASDK in your app.</p>
</article>
<div class="docs-prevnext">
<a class="docs-prev" href="/guide/2/">&larr; prev</a>
<a class="docs-next" href="/guide/4/">next &rarr;</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -1,242 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Making the most of AsyncDisplayKit &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/4/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Making the most of AsyncDisplayKit &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/4/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Making the most of AsyncDisplayKit
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/4-making-the-most-of-asdk.md" target="_blank">[edit]</a>
</h1>
</header>
<article class="post-content">
<h2>A note on optimisation</h2>
<p>AsyncDisplayKit is powerful and flexible, but it is not a panacea. If your app
has a complex image- or text-heavy user interface, ASDK can definitely help
improve its performance &mdash; but if you&#39;re blocking the main thread on
network requests, you should consider rearchitecting a few things first. :]</p>
<p>So why is it worthwhile to change the way we do view layout and rendering,
given that UIKit has always been locked to the main thread and performant iOS
apps have been shipping since iPhone&#39;s launch?</p>
<h3>Modern animations</h3>
<p>Until iOS 7, static animations (à la <code>+[UIView
animateWithDuration:animations:]</code>) were the standard. The post-skeuomorphism
redesign brought with it highly-interactive, physics-based animations, with
springs joining the ranks of constant animation functions like
<code>UIViewAnimationOptionCurveEaseInOut</code>.</p>
<p>Classic animations aren&#39;t actually executed in your app. They&#39;re executed
out-of-process, in the high-priority Core Animation render server. Thanks to
pre-emptive multitasking, an app can block its main thread continuously without
causing the animation to drop a single frame.</p>
<p>Critically, dynamic animations can&#39;t be offloaded the same way, and both
<a href="https://github.com/facebook/pop">pop</a> and UIKit Dynamics execute physics
simulations on your app&#39;s main thread. This is because executing arbitrary
code in the render server would introduce unacceptable latency, even if it
could be done securely.</p>
<p>Physics-based animations are often interactive, letting you start an animation
and interrupt it before it completes. Paper lets you fling objects across the
screen and catch them before they land, or grab a view that&#39;s being pulled by a
spring and tear it off. This requires processing touch events and updating
animation targets in realtime &mdash; even short delays for inter-process
communication would shatter the illusion.</p>
<p>(Fun fact: Inertial scrolling is also a physics animation! UIScrollView has
always implemented its animations on the main thread, which is why stuttery
scrolling is the hallmark of a slow app.)</p>
<h3>The main-thread bottleneck</h3>
<p>Physics animations aren&#39;t the only thing that need to happen on the main
thread. The main thread&#39;s <a href="https://developer.apple.com/library/ios/documentation/cocoa/conceptual/multithreading/runloopmanagement/runloopmanagement.html">run
loop</a>
is responsible for handling touch events and initiating drawing operations
&mdash; and with UIKit in the mix, it also has to render text, decode images,
and do any other custom drawing (e.g., using Core Graphics).</p>
<p>If an iteration of the main thread&#39;s run loop takes too long, it will drop an
animation frame and may fail to handle touch events in time. Each iteration of
the run loop must complete within 16ms in order to drive 60fps animations, and
your own code typically has less than 10ms to execute. This means that the
best way to keep your app smooth and responsive is to do as little work on the
main thread as possible.</p>
<p>What&#39;s more, the main thread only executes on one core! Single-threaded view
hierarchies can&#39;t take advantage of the multicore CPUs in all modern iOS
devices. This is important for more than just performance reasons &mdash; it&#39;s
also critical for battery life. Running the CPU on all cores for a short time
is preferable to running one core for an extended amount of time: if the
processor can <em>race to sleep</em> by finishing its work and idling faster, it can
spend more time in a low-power mode, improving battery life.</p>
<h2>When to go asynchronous</h2>
<p>AsyncDisplayKit really shines when used fully asynchronously, shifting both
measurement and rendering passes off the main thread and onto multiple cores.
This requires a completely node-based hierarchy. Just as degrading from
UIViews to CALayers disables view-specific functionality like touch handling
from that point on, degrading from nodes to views disables async behaviour.</p>
<p>You don&#39;t, however, need to convert your app&#39;s entire view hierarchy to nodes.
In fact, you shouldn&#39;t! Asynchronously bringing up your app&#39;s core UI, like
navigation elements or tab bars, would be a very confusing experience. Those
elements of your apps can still be nodes, but should be fully synchronous to
guarantee a fully-usable interface as quickly as possible. (This is why
UIWindow has no node equivalent.)</p>
<p>There are two key situations where asynchronous hierarchies excel:</p>
<ol>
<li><p><em>Parallelisation</em>. Measuring and rendering UITableViewCells (or your app&#39;s
equivalent, e.g., story cards in Paper) are embarrassingly parallel
problems. Table cells typically have a fixed width and variable height
determined by their contents &mdash; the argument to <code>-measure:</code> for one
cell doesn&#39;t depend on any other cells&#39; calculations, so we can enqueue an
arbitrary number of cell measurement passes at once.</p></li>
<li><p><em>Preloading</em>. An app with five tabs should synchronously load the first
one so content is ready to go as quickly as possible. Once this is
complete and the CPU is idle, why not asynchronously prepare the other tabs
so the user doesn&#39;t have to wait? This is inconvenient with views, but
very easy with nodes.</p></li>
</ol>
<p>Paper&#39;s asynchronous rendering starts at the story strip. You should profile
your app and watch how people use it in the wild to decide what combination of
synchrony and asynchrony yields the best user experience.</p>
<h2>Additional optimisations</h2>
<p>Complex hierarchies &mdash; even when rendered asynchronously &mdash; can
impose a cost because of the sheer number of views involved. Working around
this can be painful, but AsyncDisplayKit makes it easy!</p>
<ul>
<li><p><em>Layer-backing</em>. In some cases, you can substantially improve your app&#39;s
performance by using layers instead of views. Manually converting
view-based code to layers is laborious due to the difference in APIs.
Worse, if at some point you need to enable touch handling or other
view-specific functionality, you have to manually convert everything back
(and risk regressions!).</p>
<p>With nodes, converting an entire subtree from views to layers is as simple
as...</p>
<div class="highlight"><pre><code class="language-text" data-lang="text">rootNode.layerBacked = YES;
</code></pre></div>
<p>...and if you need to go back, it&#39;s as simple as deleting one line. We
recommend enabling layer-backing as a matter of course in any custom node
that doesn&#39;t need touch handling.</p></li>
<li><p><em>Precompositing</em>. Flattening an entire view hierarchy into a single layer
also improves performance, but comes with a hit to maintainability and
hierarchy-based reasoning. Nodes can do this for you too!</p>
<div class="highlight"><pre><code class="language-text" data-lang="text">rootNode.shouldRasterizeDescendants = YES;
</code></pre></div>
<p>...will cause the entire node hierarchy from that point on to be rendered
into one layer.</p></li>
</ul>
<p>Next up: AsyncDisplayKit, under the hood.</p>
</article>
<div class="docs-prevnext">
<a class="docs-prev" href="/guide/3/">&larr; prev</a>
<a class="docs-next" href="/guide/5/">next &rarr;</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -1,187 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Under the hood &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/5/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Under the hood &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/5/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Under the hood
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/5-under-the-hood.md" target="_blank">[edit]</a>
</h1>
</header>
<article class="post-content">
<h2>Node architecture</h2>
<p><em>(Skip to the next section if you&#39;re not interested in AsyncDisplayKit implementation details.)</em></p>
<p>We&#39;ve described nodes as an abstraction over views and layers, and shown how to
interact with the underlying UIViews and CALayers when necessary. Nodes don&#39;t
wrap or vend their UIKit counterparts, though &mdash; an ASImageNode&#39;s <code>.view</code>
is not a UIImageView! So how do nodes work?</p>
<p><strong>NOTE:</strong> Classes whose names begin with <code>_</code> are private. Don&#39;t use them
directly!</p>
<p>Creating a node doesn&#39;t create its underlying view-layer pair. This is why you
can create nodes cheaply and on background threads. When you use a UIView or
CALayer property on a node, you&#39;re actually interacting with a proxy object,
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Private/_ASPendingState.h"><code>_ASPendingState</code></a>,
that&#39;s preconfigured to match UIView and CALayer defaults.</p>
<p>The first access to a node&#39;s <code>.view</code> or <code>.layer</code> property causes both to be
initialised and configured with the node&#39;s current state. If it has subnodes,
they are recursively loaded as well. Once a node has been loaded, the proxy
object is destroyed and the node becomes main-thread-affined &mdash; its
properties will update the underlying view directly. (Layer-backed nodes do
the same, not loading views.)</p>
<p>Nodes are powered by
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Details/_ASDisplayLayer.h"><code>_ASDisplayLayer</code></a>
and
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Details/_ASDisplayView.h"><code>_ASDisplayView</code></a>.
These are lightweight to create and add to their respective hierarchies, and
provide integration points that allow nodes to act as full-fledged views or
layers. It&#39;s possible to create nodes that are backed by custom view or layer
classes, but doing so is strongly discouraged as it disables the majority of
ASDK&#39;s functionality. </p>
<p>When Core Animation asks an <code>_ASDisplayLayer</code> to draw itself, the request is
forwarded to its node. Unless asynchronous display has been disabled, the
actual draw call won&#39;t happen immediately or on the main thread. Instead, a
display block will be added to a background queue. These blocks are executed
in parallel, but you can enable <code>ASDISPLAYNODE_DELAY_DISPLAY</code> in
<a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/Private/ASDisplayNode%2BAsyncDisplay.mm"><code>ASDisplayNode(AsyncDisplay)</code></a>
to serialise the render system for debugging.</p>
<p>Common UIView subclass hooks are forwarded from <code>_ASDisplayView</code> to its
underlying node, including touch handling, hit-testing, and gesture recogniser
delegate calls. Because an <code>_ASDisplayView</code>&#39;s layer is an <code>_ASDisplayLayer</code>,
view-backed nodes also participate in asynchronous display.</p>
<h2>In practice</h2>
<p>What does this mean for your custom nodes?</p>
<p>You can implement methods like <code>-touchesBegan:withEvent:</code> /
<code>touchesMoved:withEvent:</code> / <code>touchesEnded:withEvent:</code> /
<code>touchesCancelled:withEvent:</code> in your nodes exactly as you would in a UIView
subclass. If you find you need a subclass hook that hasn&#39;t already been
provided, please file an issue on GitHub &mdash; or add it yourself and submit a
pull request!</p>
<p>If you need to interact or configure your node&#39;s underlying view or layer,
don&#39;t do so in <code>-init</code>. Instead, override <code>-didLoad</code> and check if you&#39;re
layer-backed:</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">didLoad</span>
<span class="p">{</span>
<span class="p">[</span><span class="nb">super</span> <span class="n">didLoad</span><span class="p">];</span>
<span class="c1">// add a gesture recogniser, if we have a view to add it to</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">self</span><span class="p">.</span><span class="n">layerBacked</span><span class="p">)</span> <span class="p">{</span>
<span class="n">_gestureRecogniser</span> <span class="o">=</span> <span class="p">[[</span><span class="bp">UITapGestureRecognizer</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithTarget</span><span class="p">:</span><span class="nb">self</span>
<span class="nl">action</span><span class="p">:</span><span class="k">@selector</span><span class="p">(</span><span class="nl">_tap</span><span class="p">:)];</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2><em>fin.</em></h2>
<p>Thanks for reading! If you have any questions, please file a GitHub issue or
post in the <a href="https://www.facebook.com/groups/551597518288687">Facebook group</a>.
We&#39;d love to hear from you.</p>
</article>
<div class="docs-prevnext">
<a class="docs-prev" href="/guide/4/">&larr; prev</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -1,242 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Getting started &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Getting started &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Getting started
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/1-introduction.md" target="_blank">[edit]</a>
</h1>
</header>
<article class="post-content">
<h2>Concepts</h2>
<p>AsyncDisplayKit&#39;s basic unit is the <em>node</em>. ASDisplayNode is an abstraction
over UIView, which in turn is an abstraction over CALayer. Unlike views, which
can only be used on the main thread, nodes are thread-safe: you can
instantiate and configure entire hierarchies of them in parallel on background
threads.</p>
<p>To keep its user interface smooth and responsive, your app should render at 60
frames per second &mdash; the gold standard on iOS. This means the main thread
has one-sixtieth of a second to push each frame. That&#39;s 16 milliseconds to
execute all layout and drawing code! And because of system overhead, your code
usually has less than ten milliseconds to run before it causes a frame drop.</p>
<p>AsyncDisplayKit lets you move image decoding, text sizing and rendering, and
other expensive UI operations off the main thread. It has other tricks up its
sleeve too... but we&#39;ll get to that later. :]</p>
<h2>Nodes as drop-in view replacements</h2>
<p>If you&#39;re used to working with views, you already know how to use nodes. The
node API is similar to UIView&#39;s, with some additional conveniences &mdash; for
example, you can access common CALayer properties directly. To add a node to
an existing view or layer hierarchy, use its <code>node.view</code> or <code>node.layer</code>.</p>
<p>AsyncDisplayKit&#39;s core components include:</p>
<ul>
<li> <em>ASDisplayNode</em>. Counterpart to UIView &mdash; subclass to make custom nodes.</li>
<li> <em>ASControlNode</em>. Analogous to UIControl &mdash; subclass to make buttons.</li>
<li> <em>ASImageNode</em>. Like UIImageView &mdash; decodes images asynchronously.</li>
<li> <em>ASTextNode</em>. Like UITextView &mdash; built on TextKit with full-featured
rich text support.</li>
<li> <em>ASTableView</em> and <em>ASCollectionView</em>. UITableView and UICollectionView
subclasses that support nodes.</li>
</ul>
<p>You can use these as drop-in replacements for their UIKit counterparts. While
ASDK works most effectively with fully node-based hierarchies, even replacing
individual views with nodes can improve performance.</p>
<p>Let&#39;s look at an example.</p>
<p>We&#39;ll start out by using nodes synchronously on the main thread &mdash; the
same way you already use views. This code is a familiar sight in custom view
controller <code>-loadView</code> implementations:</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="n">_imageView</span> <span class="o">=</span> <span class="p">[[</span><span class="bp">UIImageView</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">_imageView</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="p">[</span><span class="bp">UIImage</span> <span class="nl">imageNamed</span><span class="p">:</span><span class="s">@&quot;hello&quot;</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="mf">10.0f</span><span class="p">,</span> <span class="mf">10.0f</span><span class="p">,</span> <span class="mf">40.0f</span><span class="p">,</span> <span class="mf">40.0f</span><span class="p">);</span>
<span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">view</span> <span class="nl">addSubview</span><span class="p">:</span><span class="n">_imageView</span><span class="p">];</span>
</code></pre></div>
<p>We can replace it with the following node-based code:</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="n">_imageNode</span> <span class="o">=</span> <span class="p">[[</span><span class="n">ASImageNode</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">_imageNode</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="p">[</span><span class="bp">UIColor</span> <span class="n">lightGrayColor</span><span class="p">];</span>
<span class="n">_imageNode</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="p">[</span><span class="bp">UIImage</span> <span class="nl">imageNamed</span><span class="p">:</span><span class="s">@&quot;hello&quot;</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="mf">10.0f</span><span class="p">,</span> <span class="mf">10.0f</span><span class="p">,</span> <span class="mf">40.0f</span><span class="p">,</span> <span class="mf">40.0f</span><span class="p">);</span>
<span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">view</span> <span class="nl">addSubview</span><span class="p">:</span><span class="n">_imageNode</span><span class="p">.</span><span class="n">view</span><span class="p">];</span>
</code></pre></div>
<p>This doesn&#39;t take advantage of ASDK&#39;s asynchronous sizing and layout
functionality, but it&#39;s already an improvement. The first block of code
synchronously decodes <code>hello.png</code> on the main thread; the second starts
decoding the image on a background thread, possibly on a different CPU core.</p>
<p>(Note that we&#39;re setting a placeholder background colour on the node, &quot;holding
its place&quot; onscreen until the real content appears. This works well with
images but less so with text &mdash; people expect text to appear instantly,
with images loading in after a slight delay. We&#39;ll discuss techniques to
improve this later on.)</p>
<h2>Button nodes</h2>
<p>ASImageNode and ASTextNode both inherit from ASControlNode, so you can use them
as buttons. Let&#39;s say we&#39;re making a music player and we want to add a
(non-skeuomorphic, iOS 7-style) shuffle button:</p>
<p><a href="/assets/guide/1-shuffle.png"><img src="/assets/guide/1-shuffle-crop.png" alt="shuffle"></a></p>
<p>Our view controller will look something 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="kt">void</span><span class="p">)</span><span class="nf">viewDidLoad</span>
<span class="p">{</span>
<span class="p">[</span><span class="nb">super</span> <span class="n">viewDidLoad</span><span class="p">];</span>
<span class="c1">// attribute a string</span>
<span class="bp">NSDictionary</span> <span class="o">*</span><span class="n">attrs</span> <span class="o">=</span> <span class="l">@{</span>
<span class="nl">NSFontAttributeName</span><span class="p">:</span> <span class="p">[</span><span class="bp">UIFont</span> <span class="nl">systemFontOfSize</span><span class="p">:</span><span class="mf">12.0f</span><span class="p">],</span>
<span class="nl">NSForegroundColorAttributeName</span><span class="p">:</span> <span class="p">[</span><span class="bp">UIColor</span> <span class="n">redColor</span><span class="p">],</span>
<span class="l">}</span><span class="p">;</span>
<span class="bp">NSAttributedString</span> <span class="o">*</span><span class="n">string</span> <span class="o">=</span> <span class="p">[[</span><span class="bp">NSAttributedString</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithString</span><span class="p">:</span><span class="s">@&quot;shuffle&quot;</span>
<span class="nl">attributes</span><span class="p">:</span><span class="n">attrs</span><span class="p">];</span>
<span class="c1">// create the node</span>
<span class="n">_shuffleNode</span> <span class="o">=</span> <span class="p">[[</span><span class="n">ASTextNode</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">_shuffleNode</span><span class="p">.</span><span class="n">attributedString</span> <span class="o">=</span> <span class="n">string</span><span class="p">;</span>
<span class="c1">// configure the button</span>
<span class="n">_shuffleNode</span><span class="p">.</span><span class="n">userInteractionEnabled</span> <span class="o">=</span> <span class="nb">YES</span><span class="p">;</span> <span class="c1">// opt into touch handling</span>
<span class="p">[</span><span class="n">_shuffleNode</span> <span class="nl">addTarget</span><span class="p">:</span><span class="nb">self</span>
<span class="nl">action</span><span class="p">:</span><span class="k">@selector</span><span class="p">(</span><span class="nl">buttonTapped</span><span class="p">:)</span>
<span class="nl">forControlEvents</span><span class="p">:</span><span class="n">ASControlNodeEventTouchUpInside</span><span class="p">];</span>
<span class="c1">// size all the things</span>
<span class="bp">CGRect</span> <span class="n">b</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">bounds</span><span class="p">;</span> <span class="c1">// convenience</span>
<span class="bp">CGSize</span> <span class="n">size</span> <span class="o">=</span> <span class="p">[</span><span class="n">_shuffleNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="n">b</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">FLT_MAX</span><span class="p">)];</span>
<span class="bp">CGPoint</span> <span class="n">origin</span> <span class="o">=</span> <span class="n">CGPointMake</span><span class="p">(</span><span class="n">roundf</span><span class="p">(</span> <span class="p">(</span><span class="n">b</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">size</span><span class="p">.</span><span class="n">width</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0f</span> <span class="p">),</span>
<span class="n">roundf</span><span class="p">(</span> <span class="p">(</span><span class="n">b</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="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0f</span> <span class="p">));</span>
<span class="n">_shuffleNode</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">origin</span><span class="p">,</span> <span class="n">size</span> <span class="p">};</span>
<span class="c1">// add to our view</span>
<span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">view</span> <span class="nl">addSubview</span><span class="p">:</span><span class="n">_shuffleNode</span><span class="p">.</span><span class="n">view</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">buttonTapped:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">sender</span>
<span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">@&quot;tapped!&quot;</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>This works as you would expect. Unfortunately, this button is only 14&frac12;
points tall &mdash; nowhere near the standard 44&times;44 minimum tap target
size &mdash; and it&#39;s very difficult to tap. We could solve this by
subclassing the text node and overriding <code>-hitTest:withEvent:</code>. We could even
force the text view to have a minimum height during layout. But wouldn&#39;t it be
nice if there were a more elegant way?</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"> <span class="c1">// size all the things</span>
<span class="cm">/* ... */</span>
<span class="c1">// make the tap target taller</span>
<span class="n">CGFloat</span> <span class="n">extendY</span> <span class="o">=</span> <span class="n">roundf</span><span class="p">(</span> <span class="p">(</span><span class="mf">44.0f</span> <span class="o">-</span> <span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0f</span> <span class="p">);</span>
<span class="n">_shuffleNode</span><span class="p">.</span><span class="n">hitTestSlop</span> <span class="o">=</span> <span class="n">UIEdgeInsetsMake</span><span class="p">(</span><span class="o">-</span><span class="n">extendY</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span> <span class="o">-</span><span class="n">extendY</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">);</span>
</code></pre></div>
<p>Et voilà! <em>Hit-test slops</em> work on all nodes, and are a nice example of what
this new abstraction enables.</p>
<p>Next up, making your own nodes!</p>
</article>
<div class="docs-prevnext">
<a class="docs-next" href="/guide/2/">next &rarr;</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>

View File

@@ -1,242 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width initial-scale=1" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta property="og:title" content="Getting started &mdash; AsyncDisplayKit">
<meta property="og:type" content="website">
<meta property="og:url" content="http://asyncdisplaykit.org/guide/">
<meta property="og:image" content="http://asyncdisplaykit.org/assets/logo-square.png">
<meta property="og:description" content="Smooth asynchronous user interfaces for iOS apps">
<title>Getting started &mdash; AsyncDisplayKit</title>
<meta name="description" content="Smooth asynchronous user interfaces for iOS apps.">
<link rel="stylesheet" href="/css/main.css">
<link rel="canonical" href="http://asyncdisplaykit.org/guide/">
<script>
if (location.host == "facebook.github.io") {
// get outta here
location = 'http://asyncdisplaykit.org';
}
</script>
</head>
<body>
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="/">AsyncDisplayKit</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link page-link-active" href="/guide">guide</a>
<a class="page-link" href="/appledoc">api</a>
<a class="page-link" href="https://github.com/facebook/AsyncDisplayKit">github</a>
<a class="page-link" href="/resources.html">resources</a>
</div>
</nav>
</div>
</header>
<div class="page-content">
<div class="wrapper">
<div class="post">
<header class="post-header">
<h1 class="post-title">
Getting started
<a class="edit-page-link" href="https://github.com/facebook/AsyncDisplayKit/tree/master/docs/guide/1-introduction.md" target="_blank">[edit]</a>
</h1>
</header>
<article class="post-content">
<h2>Concepts</h2>
<p>AsyncDisplayKit&#39;s basic unit is the <em>node</em>. ASDisplayNode is an abstraction
over UIView, which in turn is an abstraction over CALayer. Unlike views, which
can only be used on the main thread, nodes are thread-safe: you can
instantiate and configure entire hierarchies of them in parallel on background
threads.</p>
<p>To keep its user interface smooth and responsive, your app should render at 60
frames per second &mdash; the gold standard on iOS. This means the main thread
has one-sixtieth of a second to push each frame. That&#39;s 16 milliseconds to
execute all layout and drawing code! And because of system overhead, your code
usually has less than ten milliseconds to run before it causes a frame drop.</p>
<p>AsyncDisplayKit lets you move image decoding, text sizing and rendering, and
other expensive UI operations off the main thread. It has other tricks up its
sleeve too... but we&#39;ll get to that later. :]</p>
<h2>Nodes as drop-in view replacements</h2>
<p>If you&#39;re used to working with views, you already know how to use nodes. The
node API is similar to UIView&#39;s, with some additional conveniences &mdash; for
example, you can access common CALayer properties directly. To add a node to
an existing view or layer hierarchy, use its <code>node.view</code> or <code>node.layer</code>.</p>
<p>AsyncDisplayKit&#39;s core components include:</p>
<ul>
<li> <em>ASDisplayNode</em>. Counterpart to UIView &mdash; subclass to make custom nodes.</li>
<li> <em>ASControlNode</em>. Analogous to UIControl &mdash; subclass to make buttons.</li>
<li> <em>ASImageNode</em>. Like UIImageView &mdash; decodes images asynchronously.</li>
<li> <em>ASTextNode</em>. Like UITextView &mdash; built on TextKit with full-featured
rich text support.</li>
<li> <em>ASTableView</em> and <em>ASCollectionView</em>. UITableView and UICollectionView
subclasses that support nodes.</li>
</ul>
<p>You can use these as drop-in replacements for their UIKit counterparts. While
ASDK works most effectively with fully node-based hierarchies, even replacing
individual views with nodes can improve performance.</p>
<p>Let&#39;s look at an example.</p>
<p>We&#39;ll start out by using nodes synchronously on the main thread &mdash; the
same way you already use views. This code is a familiar sight in custom view
controller <code>-loadView</code> implementations:</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="n">_imageView</span> <span class="o">=</span> <span class="p">[[</span><span class="bp">UIImageView</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">_imageView</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="p">[</span><span class="bp">UIImage</span> <span class="nl">imageNamed</span><span class="p">:</span><span class="s">@&quot;hello&quot;</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="mf">10.0f</span><span class="p">,</span> <span class="mf">10.0f</span><span class="p">,</span> <span class="mf">40.0f</span><span class="p">,</span> <span class="mf">40.0f</span><span class="p">);</span>
<span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">view</span> <span class="nl">addSubview</span><span class="p">:</span><span class="n">_imageView</span><span class="p">];</span>
</code></pre></div>
<p>We can replace it with the following node-based code:</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="n">_imageNode</span> <span class="o">=</span> <span class="p">[[</span><span class="n">ASImageNode</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">_imageNode</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="p">[</span><span class="bp">UIColor</span> <span class="n">lightGrayColor</span><span class="p">];</span>
<span class="n">_imageNode</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="p">[</span><span class="bp">UIImage</span> <span class="nl">imageNamed</span><span class="p">:</span><span class="s">@&quot;hello&quot;</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="mf">10.0f</span><span class="p">,</span> <span class="mf">10.0f</span><span class="p">,</span> <span class="mf">40.0f</span><span class="p">,</span> <span class="mf">40.0f</span><span class="p">);</span>
<span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">view</span> <span class="nl">addSubview</span><span class="p">:</span><span class="n">_imageNode</span><span class="p">.</span><span class="n">view</span><span class="p">];</span>
</code></pre></div>
<p>This doesn&#39;t take advantage of ASDK&#39;s asynchronous sizing and layout
functionality, but it&#39;s already an improvement. The first block of code
synchronously decodes <code>hello.png</code> on the main thread; the second starts
decoding the image on a background thread, possibly on a different CPU core.</p>
<p>(Note that we&#39;re setting a placeholder background colour on the node, &quot;holding
its place&quot; onscreen until the real content appears. This works well with
images but less so with text &mdash; people expect text to appear instantly,
with images loading in after a slight delay. We&#39;ll discuss techniques to
improve this later on.)</p>
<h2>Button nodes</h2>
<p>ASImageNode and ASTextNode both inherit from ASControlNode, so you can use them
as buttons. Let&#39;s say we&#39;re making a music player and we want to add a
(non-skeuomorphic, iOS 7-style) shuffle button:</p>
<p><a href="/assets/guide/1-shuffle.png"><img src="/assets/guide/1-shuffle-crop.png" alt="shuffle"></a></p>
<p>Our view controller will look something 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="kt">void</span><span class="p">)</span><span class="nf">viewDidLoad</span>
<span class="p">{</span>
<span class="p">[</span><span class="nb">super</span> <span class="n">viewDidLoad</span><span class="p">];</span>
<span class="c1">// attribute a string</span>
<span class="bp">NSDictionary</span> <span class="o">*</span><span class="n">attrs</span> <span class="o">=</span> <span class="l">@{</span>
<span class="nl">NSFontAttributeName</span><span class="p">:</span> <span class="p">[</span><span class="bp">UIFont</span> <span class="nl">systemFontOfSize</span><span class="p">:</span><span class="mf">12.0f</span><span class="p">],</span>
<span class="nl">NSForegroundColorAttributeName</span><span class="p">:</span> <span class="p">[</span><span class="bp">UIColor</span> <span class="n">redColor</span><span class="p">],</span>
<span class="l">}</span><span class="p">;</span>
<span class="bp">NSAttributedString</span> <span class="o">*</span><span class="n">string</span> <span class="o">=</span> <span class="p">[[</span><span class="bp">NSAttributedString</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithString</span><span class="p">:</span><span class="s">@&quot;shuffle&quot;</span>
<span class="nl">attributes</span><span class="p">:</span><span class="n">attrs</span><span class="p">];</span>
<span class="c1">// create the node</span>
<span class="n">_shuffleNode</span> <span class="o">=</span> <span class="p">[[</span><span class="n">ASTextNode</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">_shuffleNode</span><span class="p">.</span><span class="n">attributedString</span> <span class="o">=</span> <span class="n">string</span><span class="p">;</span>
<span class="c1">// configure the button</span>
<span class="n">_shuffleNode</span><span class="p">.</span><span class="n">userInteractionEnabled</span> <span class="o">=</span> <span class="nb">YES</span><span class="p">;</span> <span class="c1">// opt into touch handling</span>
<span class="p">[</span><span class="n">_shuffleNode</span> <span class="nl">addTarget</span><span class="p">:</span><span class="nb">self</span>
<span class="nl">action</span><span class="p">:</span><span class="k">@selector</span><span class="p">(</span><span class="nl">buttonTapped</span><span class="p">:)</span>
<span class="nl">forControlEvents</span><span class="p">:</span><span class="n">ASControlNodeEventTouchUpInside</span><span class="p">];</span>
<span class="c1">// size all the things</span>
<span class="bp">CGRect</span> <span class="n">b</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">bounds</span><span class="p">;</span> <span class="c1">// convenience</span>
<span class="bp">CGSize</span> <span class="n">size</span> <span class="o">=</span> <span class="p">[</span><span class="n">_shuffleNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="n">b</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">FLT_MAX</span><span class="p">)];</span>
<span class="bp">CGPoint</span> <span class="n">origin</span> <span class="o">=</span> <span class="n">CGPointMake</span><span class="p">(</span><span class="n">roundf</span><span class="p">(</span> <span class="p">(</span><span class="n">b</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">size</span><span class="p">.</span><span class="n">width</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0f</span> <span class="p">),</span>
<span class="n">roundf</span><span class="p">(</span> <span class="p">(</span><span class="n">b</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="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0f</span> <span class="p">));</span>
<span class="n">_shuffleNode</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">origin</span><span class="p">,</span> <span class="n">size</span> <span class="p">};</span>
<span class="c1">// add to our view</span>
<span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">view</span> <span class="nl">addSubview</span><span class="p">:</span><span class="n">_shuffleNode</span><span class="p">.</span><span class="n">view</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">buttonTapped:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">sender</span>
<span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">@&quot;tapped!&quot;</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>This works as you would expect. Unfortunately, this button is only 14&frac12;
points tall &mdash; nowhere near the standard 44&times;44 minimum tap target
size &mdash; and it&#39;s very difficult to tap. We could solve this by
subclassing the text node and overriding <code>-hitTest:withEvent:</code>. We could even
force the text view to have a minimum height during layout. But wouldn&#39;t it be
nice if there were a more elegant way?</p>
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"> <span class="c1">// size all the things</span>
<span class="cm">/* ... */</span>
<span class="c1">// make the tap target taller</span>
<span class="n">CGFloat</span> <span class="n">extendY</span> <span class="o">=</span> <span class="n">roundf</span><span class="p">(</span> <span class="p">(</span><span class="mf">44.0f</span> <span class="o">-</span> <span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0f</span> <span class="p">);</span>
<span class="n">_shuffleNode</span><span class="p">.</span><span class="n">hitTestSlop</span> <span class="o">=</span> <span class="n">UIEdgeInsetsMake</span><span class="p">(</span><span class="o">-</span><span class="n">extendY</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span> <span class="o">-</span><span class="n">extendY</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">);</span>
</code></pre></div>
<p>Et voilà! <em>Hit-test slops</em> work on all nodes, and are a nice example of what
this new abstraction enables.</p>
<p>Next up, making your own nodes!</p>
</article>
<div class="docs-prevnext">
<a class="docs-next" href="/guide/2/">next &rarr;</a>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-left">
<p class="text">a Facebook &amp; Instagram collaboration &#x2665;</p>
</div>
<div class="footer-col footer-col-right">
<p class="text">
&copy; 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
</p>
</div>
</div>
</div>
</footer>
</body>
</html>