mirror of
https://github.com/HackPlan/AsyncDisplayKit.git
synced 2026-04-14 12:07:05 +08:00
last removal
This commit is contained in:
@@ -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 — 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 — 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't ideal. We're sizing our subviews twice — once to figure out
|
||||
how big our view needs to be and once when laying it out — and while our
|
||||
layout arithmetic is cheap and quick, we're also blocking the main thread on
|
||||
expensive text sizing.</p>
|
||||
|
||||
<p>We could improve the situation by manually cacheing our subviews' sizes, but
|
||||
that solution comes with its own set of problems. Just adding <code>_imageSize</code> and
|
||||
<code>_textSize</code> ivars wouldn't be enough: for example, if the text were to change,
|
||||
we'd need to recompute its size. The boilerplate would quickly become
|
||||
untenable.</p>
|
||||
|
||||
<p>Further, even with a cache, we'll still be blocking the main thread on sizing
|
||||
<em>sometimes</em>. We could try to shift sizing to a background thread with
|
||||
<code>dispatch_async()</code>, but even if our own code is thread-safe, UIView methods are
|
||||
documented to <a href="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/index.html">only work on the main
|
||||
thread</a>:</p>
|
||||
|
||||
<blockquote>
|
||||
<p>Manipulations to your application’s user interface must occur on the main
|
||||
thread. Thus, you should always call the methods of the UIView class from
|
||||
code running in the main thread of your application. The only time this may
|
||||
not be strictly necessary is when creating the view object itself but all
|
||||
other manipulations should occur on the main thread.</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's a
|
||||
laborious duplication of work. Further, if UITextView's layout behaviour
|
||||
changes in an iOS update, our sizing code will break. (And did we mention that
|
||||
TextKit isn't thread-safe either?)</p>
|
||||
|
||||
<h2>Node hierarchies</h2>
|
||||
|
||||
<p>Enter AsyncDisplayKit. Our custom node looks like this:</p>
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="cp">#import <AsyncDisplayKit/AsyncDisplayKit+Subclasses.h></span>
|
||||
|
||||
<span class="p">...</span>
|
||||
|
||||
<span class="c1">// perform expensive sizing operations on a background thread</span>
|
||||
<span class="o">-</span> <span class="p">(</span><span class="bp">CGSize</span><span class="p">)</span><span class="nl">calculateSizeThatFits</span><span class="p">:(</span><span class="bp">CGSize</span><span class="p">)</span><span class="n">constrainedSize</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// size the image</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_imageNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">constrainedSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// size the text node</span>
|
||||
<span class="bp">CGSize</span> <span class="n">maxTextSize</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">constrainedSize</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span>
|
||||
<span class="n">constrainedSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_textNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">maxTextSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// make sure everything fits</span>
|
||||
<span class="n">CGFloat</span> <span class="n">minHeight</span> <span class="o">=</span> <span class="n">MAX</span><span class="p">(</span><span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">,</span> <span class="n">textSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="k">return</span> <span class="nf">CGSizeMake</span><span class="p">(</span><span class="n">constrainedSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">minHeight</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1">// do as little work as possible in main-thread layout</span>
|
||||
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">layout</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// layout the image using its cached size</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="n">_imageNode</span><span class="p">.</span><span class="n">calculatedSize</span><span class="p">;</span>
|
||||
<span class="n">_imageNode</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="nb">self</span><span class="p">.</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>
|
||||
<span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// layout the text view using its cached size</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="n">_textNode</span><span class="p">.</span><span class="n">calculatedSize</span><span class="p">;</span>
|
||||
<span class="n">_textNode</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="p">(</span><span class="bp">CGRect</span><span class="p">){</span> <span class="n">CGPointZero</span><span class="p">,</span> <span class="n">textSize</span> <span class="p">};</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
<p>ASImageNode and ASTextNode, like the rest of AsyncDisplayKit, are thread-safe,
|
||||
so we can size them on background threads. The <code>-measure:</code> method is like
|
||||
<code>-sizeThatFits:</code>, but with side effects: it caches both the argument
|
||||
(<code>constrainedSizeForCalculatedSize</code>) and the result (<code>calculatedSize</code>) for
|
||||
quick access later on — like in our now-snappy <code>-layout</code> implementation.</p>
|
||||
|
||||
<p>As you can see, node hierarchies are sized and laid out in much the same way as
|
||||
their view counterparts. Custom nodes do need to be written with a few things
|
||||
in mind:</p>
|
||||
|
||||
<ul>
|
||||
<li><p>Nodes must recursively measure all of their subnodes in their
|
||||
<code>-calculateSizeThatFits:</code> implementations. Note that the <code>-measure:</code>
|
||||
machinery will only call <code>-calculateSizeThatFits:</code> if a new measurement pass
|
||||
is needed (e.g., if the constrained size has changed).</p></li>
|
||||
<li><p>Nodes should perform any other expensive pre-layout calculations in
|
||||
<code>-calculateSizeThatFits:</code>, cacheing useful intermediate results in ivars as
|
||||
appropriate.</p></li>
|
||||
<li><p>Nodes should call <code>[self invalidateCalculatedSize]</code> when necessary. For
|
||||
example, ASTextNode invalidates its calculated size when its
|
||||
<code>attributedString</code> property is changed.</p></li>
|
||||
</ul>
|
||||
|
||||
<p>For more examples of custom sizing and layout, along with a demo of
|
||||
ASTextNode's features, check out <code>BlurbNode</code> and <code>KittenNode</code> in the
|
||||
<a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens">Kittens</a>
|
||||
sample project.</p>
|
||||
|
||||
<h2>Custom drawing</h2>
|
||||
|
||||
<p>To guarantee thread safety in its highly-concurrent drawing system, the node
|
||||
drawing API diverges substantially from UIView's. Instead of implementing
|
||||
<code>-drawRect:</code>, you must:</p>
|
||||
|
||||
<ol>
|
||||
<li><p>Define an internal "draw parameters" class for your custom node. This
|
||||
class should be able to store any state your node needs to draw itself
|
||||
— it can be a plain old NSObject or even a dictionary.</p></li>
|
||||
<li><p>Return a configured instance of your draw parameters class in
|
||||
<code>-drawParametersForAsyncLayer:</code>. This method will always be called on the
|
||||
main thread.</p></li>
|
||||
<li><p>Implement either <code>+drawRect:withParameters:isCancelled:isRasterizing:</code> or
|
||||
<code>+displayWithParameters:isCancelled:</code>. Note that these are <em>class</em> methods
|
||||
that will not have access to your node's state — only the draw
|
||||
parameters object. They can be called on any thread and must be
|
||||
thread-safe.</p></li>
|
||||
</ol>
|
||||
|
||||
<p>For example, this node will draw a rainbow:</p>
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="k">@interface</span> <span class="nc">RainbowNode</span> : <span class="nc">ASDisplayNode</span>
|
||||
<span class="k">@end</span>
|
||||
|
||||
<span class="k">@implementation</span> <span class="nc">RainbowNode</span>
|
||||
|
||||
<span class="p">+</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">drawRect:</span><span class="p">(</span><span class="bp">CGRect</span><span class="p">)</span><span class="nv">bounds</span>
|
||||
<span class="nf">withParameters:</span><span class="p">(</span><span class="kt">id</span><span class="o"><</span><span class="bp">NSObject</span><span class="o">></span><span class="p">)</span><span class="nv">parameters</span>
|
||||
<span class="nf">isCancelled:</span><span class="p">(</span><span class="kt">asdisplaynode_iscancelled_block_t</span><span class="p">)</span><span class="nv">isCancelledBlock</span>
|
||||
<span class="nf">isRasterizing:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">isRasterizing</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// clear the backing store, but only if we're not rasterising into another layer</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isRasterizing</span><span class="p">)</span> <span class="p">{</span>
|
||||
<span class="p">[[</span><span class="bp">UIColor</span> <span class="n">whiteColor</span><span class="p">]</span> <span class="n">set</span><span class="p">];</span>
|
||||
<span class="n">UIRectFill</span><span class="p">(</span><span class="n">bounds</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1">// UIColor sadly lacks +indigoColor and +violetColor methods</span>
|
||||
<span class="bp">NSArray</span> <span class="o">*</span><span class="n">colors</span> <span class="o">=</span> <span class="l">@[</span> <span class="p">[</span><span class="bp">UIColor</span> <span class="n">redColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">orangeColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">yellowColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">greenColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">blueColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">purpleColor</span><span class="p">]</span> <span class="l">]</span><span class="p">;</span>
|
||||
<span class="n">CGFloat</span> <span class="n">stripeHeight</span> <span class="o">=</span> <span class="n">roundf</span><span class="p">(</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span> <span class="o">/</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">colors</span><span class="p">.</span><span class="n">count</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// draw the stripes</span>
|
||||
<span class="k">for</span> <span class="p">(</span><span class="bp">UIColor</span> <span class="o">*</span><span class="n">color</span> <span class="k">in</span> <span class="n">colors</span><span class="p">)</span> <span class="p">{</span>
|
||||
<span class="bp">CGRect</span> <span class="n">stripe</span> <span class="o">=</span> <span class="n">CGRectZero</span><span class="p">;</span>
|
||||
<span class="n">CGRectDivide</span><span class="p">(</span><span class="n">bounds</span><span class="p">,</span> <span class="o">&</span><span class="n">stripe</span><span class="p">,</span> <span class="o">&</span><span class="n">bounds</span><span class="p">,</span> <span class="n">stripeHeight</span><span class="p">,</span> <span class="n">CGRectMinYEdge</span><span class="p">);</span>
|
||||
<span class="p">[</span><span class="n">color</span> <span class="n">set</span><span class="p">];</span>
|
||||
<span class="n">UIRectFill</span><span class="p">(</span><span class="n">stripe</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="k">@end</span>
|
||||
</code></pre></div>
|
||||
<p>This could easily be extended to support vertical rainbows too, by adding a
|
||||
<code>vertical</code> property to the node, exporting it in
|
||||
<code>-drawParametersForAsyncLayer:</code>, and referencing it in
|
||||
<code>+drawRect:withParameters:isCancelled:isRasterizing:</code>. More-complex nodes can
|
||||
be supported in much the same way.</p>
|
||||
|
||||
<p>For more on custom nodes, check out the <a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASDisplayNode%2BSubclasses.h">subclassing
|
||||
header</a>
|
||||
or read on!</p>
|
||||
|
||||
</article>
|
||||
|
||||
<div class="docs-prevnext">
|
||||
|
||||
<a class="docs-prev" href="/guide/">← prev</a>
|
||||
|
||||
|
||||
<a class="docs-next" href="/guide/3/">next →</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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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 — 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 — 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're fully rendered. It'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's <code>displaysAsynchronously</code> flag to NO to disable
|
||||
ASDK's async display machinery. (AsyncDisplayKit can still improve your app's
|
||||
performance even when rendering fully synchronously — more on that
|
||||
later!)</p>
|
||||
|
||||
<p>The recommended way to use ASDK is to only add nodes to your view hierarchy
|
||||
once they'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'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 — they'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've finished displaying.</p>
|
||||
|
||||
<h2>Working range</h2>
|
||||
|
||||
<p>So far, we've only discussed asynchronous sizing: toss a "create a node
|
||||
hierarchy and measure it" block onto a background thread, then trampoline to
|
||||
the main thread to add it to the view hierarchy when that'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's content is in a scroll view or can be paged through, like
|
||||
Instagram's main feed or Paper'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'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 —
|
||||
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 — and if your app supports iOS 7, it will be used on both.</p>
|
||||
|
||||
<h2>ASTableView</h2>
|
||||
|
||||
<p>ASRangeController manages working ranges, but doesn't actually display content.
|
||||
If your content is currently rendered in a UITableView, you can convert it to
|
||||
use <code>ASTableView</code> and custom nodes — 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'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'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'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't be called when the row is about to display.</p></li>
|
||||
<li><p><code>-tableView:heightForRowAtIndexPath:</code> has been removed — ASTableView
|
||||
lets your cell nodes size themselves. This means you no longer have to
|
||||
manually duplicate or factor out layout and sizing logic for
|
||||
dynamically-sized UITableViewCells!</p></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/">← prev</a>
|
||||
|
||||
|
||||
<a class="docs-next" href="/guide/4/">next →</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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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 — 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 — 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 — but if you'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'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't actually executed in your app. They'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'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'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's being pulled by a
|
||||
spring and tear it off. This requires processing touch events and updating
|
||||
animation targets in realtime — 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't the only thing that need to happen on the main
|
||||
thread. The main thread'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
|
||||
— 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'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's more, the main thread only executes on one core! Single-threaded view
|
||||
hierarchies can't take advantage of the multicore CPUs in all modern iOS
|
||||
devices. This is important for more than just performance reasons — it'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't, however, need to convert your app's entire view hierarchy to nodes.
|
||||
In fact, you shouldn't! Asynchronously bringing up your app'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'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 — the argument to <code>-measure:</code> for one
|
||||
cell doesn't depend on any other cells' 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't have to wait? This is inconvenient with views, but
|
||||
very easy with nodes.</p></li>
|
||||
</ol>
|
||||
|
||||
<p>Paper'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 — even when rendered asynchronously — 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'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's as simple as deleting one line. We
|
||||
recommend enabling layer-backing as a matter of course in any custom node
|
||||
that doesn'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/">← prev</a>
|
||||
|
||||
|
||||
<a class="docs-next" href="/guide/5/">next →</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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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 — 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 — 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're not interested in AsyncDisplayKit implementation details.)</em></p>
|
||||
|
||||
<p>We'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't
|
||||
wrap or vend their UIKit counterparts, though — an ASImageNode'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't use them
|
||||
directly!</p>
|
||||
|
||||
<p>Creating a node doesn'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'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's preconfigured to match UIView and CALayer defaults.</p>
|
||||
|
||||
<p>The first access to a node's <code>.view</code> or <code>.layer</code> property causes both to be
|
||||
initialised and configured with the node'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 — 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'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'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'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>'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't already been
|
||||
provided, please file an issue on GitHub — or add it yourself and submit a
|
||||
pull request!</p>
|
||||
|
||||
<p>If you need to interact or configure your node's underlying view or layer,
|
||||
don't do so in <code>-init</code>. Instead, override <code>-didLoad</code> and check if you'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'd love to hear from you.</p>
|
||||
|
||||
</article>
|
||||
|
||||
<div class="docs-prevnext">
|
||||
|
||||
<a class="docs-prev" href="/guide/4/">← 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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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 — 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 — 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't ideal. We're sizing our subviews twice — once to figure out
|
||||
how big our view needs to be and once when laying it out — and while our
|
||||
layout arithmetic is cheap and quick, we're also blocking the main thread on
|
||||
expensive text sizing.</p>
|
||||
|
||||
<p>We could improve the situation by manually cacheing our subviews' sizes, but
|
||||
that solution comes with its own set of problems. Just adding <code>_imageSize</code> and
|
||||
<code>_textSize</code> ivars wouldn't be enough: for example, if the text were to change,
|
||||
we'd need to recompute its size. The boilerplate would quickly become
|
||||
untenable.</p>
|
||||
|
||||
<p>Further, even with a cache, we'll still be blocking the main thread on sizing
|
||||
<em>sometimes</em>. We could try to shift sizing to a background thread with
|
||||
<code>dispatch_async()</code>, but even if our own code is thread-safe, UIView methods are
|
||||
documented to <a href="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/index.html">only work on the main
|
||||
thread</a>:</p>
|
||||
|
||||
<blockquote>
|
||||
<p>Manipulations to your application’s user interface must occur on the main
|
||||
thread. Thus, you should always call the methods of the UIView class from
|
||||
code running in the main thread of your application. The only time this may
|
||||
not be strictly necessary is when creating the view object itself but all
|
||||
other manipulations should occur on the main thread.</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's a
|
||||
laborious duplication of work. Further, if UITextView's layout behaviour
|
||||
changes in an iOS update, our sizing code will break. (And did we mention that
|
||||
TextKit isn't thread-safe either?)</p>
|
||||
|
||||
<h2>Node hierarchies</h2>
|
||||
|
||||
<p>Enter AsyncDisplayKit. Our custom node looks like this:</p>
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="cp">#import <AsyncDisplayKit/AsyncDisplayKit+Subclasses.h></span>
|
||||
|
||||
<span class="p">...</span>
|
||||
|
||||
<span class="c1">// perform expensive sizing operations on a background thread</span>
|
||||
<span class="o">-</span> <span class="p">(</span><span class="bp">CGSize</span><span class="p">)</span><span class="nl">calculateSizeThatFits</span><span class="p">:(</span><span class="bp">CGSize</span><span class="p">)</span><span class="n">constrainedSize</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// size the image</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_imageNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">constrainedSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// size the text node</span>
|
||||
<span class="bp">CGSize</span> <span class="n">maxTextSize</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">constrainedSize</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span>
|
||||
<span class="n">constrainedSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="p">[</span><span class="n">_textNode</span> <span class="nl">measure</span><span class="p">:</span><span class="n">maxTextSize</span><span class="p">];</span>
|
||||
|
||||
<span class="c1">// make sure everything fits</span>
|
||||
<span class="n">CGFloat</span> <span class="n">minHeight</span> <span class="o">=</span> <span class="n">MAX</span><span class="p">(</span><span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">,</span> <span class="n">textSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
<span class="k">return</span> <span class="nf">CGSizeMake</span><span class="p">(</span><span class="n">constrainedSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">minHeight</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1">// do as little work as possible in main-thread layout</span>
|
||||
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">layout</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// layout the image using its cached size</span>
|
||||
<span class="bp">CGSize</span> <span class="n">imageSize</span> <span class="o">=</span> <span class="n">_imageNode</span><span class="p">.</span><span class="n">calculatedSize</span><span class="p">;</span>
|
||||
<span class="n">_imageNode</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="nb">self</span><span class="p">.</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o">-</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="mf">0.0f</span><span class="p">,</span>
|
||||
<span class="n">imageSize</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">imageSize</span><span class="p">.</span><span class="n">height</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// layout the text view using its cached size</span>
|
||||
<span class="bp">CGSize</span> <span class="n">textSize</span> <span class="o">=</span> <span class="n">_textNode</span><span class="p">.</span><span class="n">calculatedSize</span><span class="p">;</span>
|
||||
<span class="n">_textNode</span><span class="p">.</span><span class="n">frame</span> <span class="o">=</span> <span class="p">(</span><span class="bp">CGRect</span><span class="p">){</span> <span class="n">CGPointZero</span><span class="p">,</span> <span class="n">textSize</span> <span class="p">};</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
<p>ASImageNode and ASTextNode, like the rest of AsyncDisplayKit, are thread-safe,
|
||||
so we can size them on background threads. The <code>-measure:</code> method is like
|
||||
<code>-sizeThatFits:</code>, but with side effects: it caches both the argument
|
||||
(<code>constrainedSizeForCalculatedSize</code>) and the result (<code>calculatedSize</code>) for
|
||||
quick access later on — like in our now-snappy <code>-layout</code> implementation.</p>
|
||||
|
||||
<p>As you can see, node hierarchies are sized and laid out in much the same way as
|
||||
their view counterparts. Custom nodes do need to be written with a few things
|
||||
in mind:</p>
|
||||
|
||||
<ul>
|
||||
<li><p>Nodes must recursively measure all of their subnodes in their
|
||||
<code>-calculateSizeThatFits:</code> implementations. Note that the <code>-measure:</code>
|
||||
machinery will only call <code>-calculateSizeThatFits:</code> if a new measurement pass
|
||||
is needed (e.g., if the constrained size has changed).</p></li>
|
||||
<li><p>Nodes should perform any other expensive pre-layout calculations in
|
||||
<code>-calculateSizeThatFits:</code>, cacheing useful intermediate results in ivars as
|
||||
appropriate.</p></li>
|
||||
<li><p>Nodes should call <code>[self invalidateCalculatedSize]</code> when necessary. For
|
||||
example, ASTextNode invalidates its calculated size when its
|
||||
<code>attributedString</code> property is changed.</p></li>
|
||||
</ul>
|
||||
|
||||
<p>For more examples of custom sizing and layout, along with a demo of
|
||||
ASTextNode's features, check out <code>BlurbNode</code> and <code>KittenNode</code> in the
|
||||
<a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens">Kittens</a>
|
||||
sample project.</p>
|
||||
|
||||
<h2>Custom drawing</h2>
|
||||
|
||||
<p>To guarantee thread safety in its highly-concurrent drawing system, the node
|
||||
drawing API diverges substantially from UIView's. Instead of implementing
|
||||
<code>-drawRect:</code>, you must:</p>
|
||||
|
||||
<ol>
|
||||
<li><p>Define an internal "draw parameters" class for your custom node. This
|
||||
class should be able to store any state your node needs to draw itself
|
||||
— it can be a plain old NSObject or even a dictionary.</p></li>
|
||||
<li><p>Return a configured instance of your draw parameters class in
|
||||
<code>-drawParametersForAsyncLayer:</code>. This method will always be called on the
|
||||
main thread.</p></li>
|
||||
<li><p>Implement either <code>+drawRect:withParameters:isCancelled:isRasterizing:</code> or
|
||||
<code>+displayWithParameters:isCancelled:</code>. Note that these are <em>class</em> methods
|
||||
that will not have access to your node's state — only the draw
|
||||
parameters object. They can be called on any thread and must be
|
||||
thread-safe.</p></li>
|
||||
</ol>
|
||||
|
||||
<p>For example, this node will draw a rainbow:</p>
|
||||
<div class="highlight"><pre><code class="language-objective-c" data-lang="objective-c"><span class="k">@interface</span> <span class="nc">RainbowNode</span> : <span class="nc">ASDisplayNode</span>
|
||||
<span class="k">@end</span>
|
||||
|
||||
<span class="k">@implementation</span> <span class="nc">RainbowNode</span>
|
||||
|
||||
<span class="p">+</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">drawRect:</span><span class="p">(</span><span class="bp">CGRect</span><span class="p">)</span><span class="nv">bounds</span>
|
||||
<span class="nf">withParameters:</span><span class="p">(</span><span class="kt">id</span><span class="o"><</span><span class="bp">NSObject</span><span class="o">></span><span class="p">)</span><span class="nv">parameters</span>
|
||||
<span class="nf">isCancelled:</span><span class="p">(</span><span class="kt">asdisplaynode_iscancelled_block_t</span><span class="p">)</span><span class="nv">isCancelledBlock</span>
|
||||
<span class="nf">isRasterizing:</span><span class="p">(</span><span class="kt">BOOL</span><span class="p">)</span><span class="nv">isRasterizing</span>
|
||||
<span class="p">{</span>
|
||||
<span class="c1">// clear the backing store, but only if we're not rasterising into another layer</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">isRasterizing</span><span class="p">)</span> <span class="p">{</span>
|
||||
<span class="p">[[</span><span class="bp">UIColor</span> <span class="n">whiteColor</span><span class="p">]</span> <span class="n">set</span><span class="p">];</span>
|
||||
<span class="n">UIRectFill</span><span class="p">(</span><span class="n">bounds</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1">// UIColor sadly lacks +indigoColor and +violetColor methods</span>
|
||||
<span class="bp">NSArray</span> <span class="o">*</span><span class="n">colors</span> <span class="o">=</span> <span class="l">@[</span> <span class="p">[</span><span class="bp">UIColor</span> <span class="n">redColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">orangeColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">yellowColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">greenColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">blueColor</span><span class="p">],</span>
|
||||
<span class="p">[</span><span class="bp">UIColor</span> <span class="n">purpleColor</span><span class="p">]</span> <span class="l">]</span><span class="p">;</span>
|
||||
<span class="n">CGFloat</span> <span class="n">stripeHeight</span> <span class="o">=</span> <span class="n">roundf</span><span class="p">(</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span> <span class="o">/</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="n">colors</span><span class="p">.</span><span class="n">count</span><span class="p">);</span>
|
||||
|
||||
<span class="c1">// draw the stripes</span>
|
||||
<span class="k">for</span> <span class="p">(</span><span class="bp">UIColor</span> <span class="o">*</span><span class="n">color</span> <span class="k">in</span> <span class="n">colors</span><span class="p">)</span> <span class="p">{</span>
|
||||
<span class="bp">CGRect</span> <span class="n">stripe</span> <span class="o">=</span> <span class="n">CGRectZero</span><span class="p">;</span>
|
||||
<span class="n">CGRectDivide</span><span class="p">(</span><span class="n">bounds</span><span class="p">,</span> <span class="o">&</span><span class="n">stripe</span><span class="p">,</span> <span class="o">&</span><span class="n">bounds</span><span class="p">,</span> <span class="n">stripeHeight</span><span class="p">,</span> <span class="n">CGRectMinYEdge</span><span class="p">);</span>
|
||||
<span class="p">[</span><span class="n">color</span> <span class="n">set</span><span class="p">];</span>
|
||||
<span class="n">UIRectFill</span><span class="p">(</span><span class="n">stripe</span><span class="p">);</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="k">@end</span>
|
||||
</code></pre></div>
|
||||
<p>This could easily be extended to support vertical rainbows too, by adding a
|
||||
<code>vertical</code> property to the node, exporting it in
|
||||
<code>-drawParametersForAsyncLayer:</code>, and referencing it in
|
||||
<code>+drawRect:withParameters:isCancelled:isRasterizing:</code>. More-complex nodes can
|
||||
be supported in much the same way.</p>
|
||||
|
||||
<p>For more on custom nodes, check out the <a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASDisplayNode%2BSubclasses.h">subclassing
|
||||
header</a>
|
||||
or read on!</p>
|
||||
|
||||
</article>
|
||||
|
||||
<div class="docs-prevnext">
|
||||
|
||||
<a class="docs-prev" href="/guide/">← prev</a>
|
||||
|
||||
|
||||
<a class="docs-next" href="/guide/3/">next →</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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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 — 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 — 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're fully rendered. It'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's <code>displaysAsynchronously</code> flag to NO to disable
|
||||
ASDK's async display machinery. (AsyncDisplayKit can still improve your app's
|
||||
performance even when rendering fully synchronously — more on that
|
||||
later!)</p>
|
||||
|
||||
<p>The recommended way to use ASDK is to only add nodes to your view hierarchy
|
||||
once they'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'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 — they'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've finished displaying.</p>
|
||||
|
||||
<h2>Working range</h2>
|
||||
|
||||
<p>So far, we've only discussed asynchronous sizing: toss a "create a node
|
||||
hierarchy and measure it" block onto a background thread, then trampoline to
|
||||
the main thread to add it to the view hierarchy when that'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's content is in a scroll view or can be paged through, like
|
||||
Instagram's main feed or Paper'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'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 —
|
||||
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 — and if your app supports iOS 7, it will be used on both.</p>
|
||||
|
||||
<h2>ASTableView</h2>
|
||||
|
||||
<p>ASRangeController manages working ranges, but doesn't actually display content.
|
||||
If your content is currently rendered in a UITableView, you can convert it to
|
||||
use <code>ASTableView</code> and custom nodes — 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'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'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'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't be called when the row is about to display.</p></li>
|
||||
<li><p><code>-tableView:heightForRowAtIndexPath:</code> has been removed — ASTableView
|
||||
lets your cell nodes size themselves. This means you no longer have to
|
||||
manually duplicate or factor out layout and sizing logic for
|
||||
dynamically-sized UITableViewCells!</p></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/">← prev</a>
|
||||
|
||||
|
||||
<a class="docs-next" href="/guide/4/">next →</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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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 — 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 — 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 — but if you'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'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't actually executed in your app. They'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'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'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's being pulled by a
|
||||
spring and tear it off. This requires processing touch events and updating
|
||||
animation targets in realtime — 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't the only thing that need to happen on the main
|
||||
thread. The main thread'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
|
||||
— 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'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's more, the main thread only executes on one core! Single-threaded view
|
||||
hierarchies can't take advantage of the multicore CPUs in all modern iOS
|
||||
devices. This is important for more than just performance reasons — it'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't, however, need to convert your app's entire view hierarchy to nodes.
|
||||
In fact, you shouldn't! Asynchronously bringing up your app'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'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 — the argument to <code>-measure:</code> for one
|
||||
cell doesn't depend on any other cells' 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't have to wait? This is inconvenient with views, but
|
||||
very easy with nodes.</p></li>
|
||||
</ol>
|
||||
|
||||
<p>Paper'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 — even when rendered asynchronously — 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'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's as simple as deleting one line. We
|
||||
recommend enabling layer-backing as a matter of course in any custom node
|
||||
that doesn'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/">← prev</a>
|
||||
|
||||
|
||||
<a class="docs-next" href="/guide/5/">next →</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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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 — 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 — 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're not interested in AsyncDisplayKit implementation details.)</em></p>
|
||||
|
||||
<p>We'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't
|
||||
wrap or vend their UIKit counterparts, though — an ASImageNode'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't use them
|
||||
directly!</p>
|
||||
|
||||
<p>Creating a node doesn'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'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's preconfigured to match UIView and CALayer defaults.</p>
|
||||
|
||||
<p>The first access to a node's <code>.view</code> or <code>.layer</code> property causes both to be
|
||||
initialised and configured with the node'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 — 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'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'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'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>'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't already been
|
||||
provided, please file an issue on GitHub — or add it yourself and submit a
|
||||
pull request!</p>
|
||||
|
||||
<p>If you need to interact or configure your node's underlying view or layer,
|
||||
don't do so in <code>-init</code>. Instead, override <code>-didLoad</code> and check if you'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'd love to hear from you.</p>
|
||||
|
||||
</article>
|
||||
|
||||
<div class="docs-prevnext">
|
||||
|
||||
<a class="docs-prev" href="/guide/4/">← 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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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 — 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 — 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'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 — the gold standard on iOS. This means the main thread
|
||||
has one-sixtieth of a second to push each frame. That'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'll get to that later. :]</p>
|
||||
|
||||
<h2>Nodes as drop-in view replacements</h2>
|
||||
|
||||
<p>If you're used to working with views, you already know how to use nodes. The
|
||||
node API is similar to UIView's, with some additional conveniences — 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's core components include:</p>
|
||||
|
||||
<ul>
|
||||
<li> <em>ASDisplayNode</em>. Counterpart to UIView — subclass to make custom nodes.</li>
|
||||
<li> <em>ASControlNode</em>. Analogous to UIControl — subclass to make buttons.</li>
|
||||
<li> <em>ASImageNode</em>. Like UIImageView — decodes images asynchronously.</li>
|
||||
<li> <em>ASTextNode</em>. Like UITextView — 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's look at an example.</p>
|
||||
|
||||
<p>We'll start out by using nodes synchronously on the main thread — 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">@"hello"</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">@"hello"</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't take advantage of ASDK's asynchronous sizing and layout
|
||||
functionality, but it'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're setting a placeholder background colour on the node, "holding
|
||||
its place" onscreen until the real content appears. This works well with
|
||||
images but less so with text — people expect text to appear instantly,
|
||||
with images loading in after a slight delay. We'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's say we'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">@"shuffle"</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">@"tapped!"</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½
|
||||
points tall — nowhere near the standard 44×44 minimum tap target
|
||||
size — and it'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'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 →</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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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 — 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 — 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'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 — the gold standard on iOS. This means the main thread
|
||||
has one-sixtieth of a second to push each frame. That'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'll get to that later. :]</p>
|
||||
|
||||
<h2>Nodes as drop-in view replacements</h2>
|
||||
|
||||
<p>If you're used to working with views, you already know how to use nodes. The
|
||||
node API is similar to UIView's, with some additional conveniences — 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's core components include:</p>
|
||||
|
||||
<ul>
|
||||
<li> <em>ASDisplayNode</em>. Counterpart to UIView — subclass to make custom nodes.</li>
|
||||
<li> <em>ASControlNode</em>. Analogous to UIControl — subclass to make buttons.</li>
|
||||
<li> <em>ASImageNode</em>. Like UIImageView — decodes images asynchronously.</li>
|
||||
<li> <em>ASTextNode</em>. Like UITextView — 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's look at an example.</p>
|
||||
|
||||
<p>We'll start out by using nodes synchronously on the main thread — 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">@"hello"</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">@"hello"</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't take advantage of ASDK's asynchronous sizing and layout
|
||||
functionality, but it'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're setting a placeholder background colour on the node, "holding
|
||||
its place" onscreen until the real content appears. This works well with
|
||||
images but less so with text — people expect text to appear instantly,
|
||||
with images loading in after a slight delay. We'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's say we'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">@"shuffle"</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">@"tapped!"</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½
|
||||
points tall — nowhere near the standard 44×44 minimum tap target
|
||||
size — and it'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'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 →</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 & Instagram collaboration ♥</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-col footer-col-right">
|
||||
<p class="text">
|
||||
© 2014 Facebook Inc (<a href="/license/">CC-BY-4.0</a>)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user