mirror of
https://github.com/HackPlan/AsyncDisplayKit.git
synced 2026-04-07 22:35:47 +08:00
195 lines
9.3 KiB
HTML
195 lines
9.3 KiB
HTML
<!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/">
|
|
</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>
|
|
</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="mi">2</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="mi">1</span><span class="p">;</span> <span class="c1">// one 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>
|