<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Dev with Jonathan</title><link>https://jwilling.com/</link><description>Recent content on Dev with Jonathan</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 09 Nov 2015 00:00:00 +0000</lastBuildDate><atom:link href="https://jwilling.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Debugging Core Animation on OS X</title><link>https://jwilling.com/blog/debugging-core-animation-on-osx/</link><pubDate>Mon, 09 Nov 2015 00:00:00 +0000</pubDate><guid>https://jwilling.com/blog/debugging-core-animation-on-osx/</guid><description>Have you ever looked longingly at the Core Animation template in Instruments and wished it worked for OS X apps? Sadly that hasn&amp;rsquo;t happened yet, but workarounds exist. In this post, we&amp;rsquo;ll be exploring ways to debug Core Animation on the Mac so you can make your app as optimized as possible.
Since we don&amp;rsquo;t have access to the Core Animation template, we&amp;rsquo;re going to have to resort to two different tools for debugging and profiling.</description><content>&lt;p>Have you ever looked longingly at the Core Animation template in Instruments and wished it worked for OS X apps? Sadly that hasn&amp;rsquo;t happened yet, but workarounds exist. In this post, we&amp;rsquo;ll be exploring ways to debug Core Animation on the Mac so you can make your app as optimized as possible.&lt;/p>
&lt;p>Since we don&amp;rsquo;t have access to the Core Animation template, we&amp;rsquo;re going to have to resort to two different tools for debugging and profiling.&lt;/p>
&lt;h2 id="profiling-fps">Profiling FPS&lt;/h2>
&lt;p>In order to see what FPS our application is achieving, we need to use the Quartz Debug application. Unfortunately, it&amp;rsquo;s not bundled by default with Xcode. It requires a separate download. At the time of writing, it&amp;rsquo;s available &lt;a href="https://developer.apple.com/downloads/?name=graphics%20tools%20for%20xcode">in Apple&amp;rsquo;s developer downloads&lt;/a> as part of the &lt;em>Graphics Tools&lt;/em> package.&lt;/p>
&lt;p>&lt;img src="images/quartz-debug.png" alt="">&lt;/p>
&lt;p>The tool is relatively straightforward. You run Quartz Debug, then launch your application. Start an action that requires the application to perform a drawing update (such as scrolling), and watch the FPS &amp;amp; CPU meter on Quartz Debug. Sadly, this is about all of the utility this tool provides. Other debugging tools can be located in &lt;em>Window &amp;gt; Quartz Debug Settings&lt;/em>, but unfortunately they are limited in practice. However, it&amp;rsquo;s still valuable to know the framerate of your application, as it&amp;rsquo;s handy to determine if your app needs more optimization.&lt;/p>
&lt;h2 id="debugging-core-animation">Debugging Core Animation&lt;/h2>
&lt;p>This is one of the areas that&amp;rsquo;s been lacking severely on OS X. What most people don&amp;rsquo;t know is that there&amp;rsquo;s an entirely hidden way to perform Core Animation debugging in a very similar fashion to iOS.&lt;/p>
&lt;p>&lt;img src="images/debug-example.png" alt="">&lt;/p>
&lt;p>Does this coloring look familiar? If you&amp;rsquo;ve utilized Instrument&amp;rsquo;s Core Animation template for iOS, you might recognize the coloring of opaque views. So how do we get this on the Mac?&lt;/p>
&lt;p>Before 10.9, it was possible to utilize undocumented Core Animation environmental variables to enable various Core Animation debugging modes. Unfortunately this does not work in 10.9. Why? Layers in 10.9 are actually rendered out-of-process, meaning it effectively broke the debugging capability that previously existed. But thanks to another undocumented magic environmental variable (h/t &lt;a href="https://twitter.com/autorelease">Matt Sarnoff&lt;/a>), we can achieve the old behavior. Here&amp;rsquo;s the flag:&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>CA_LAYER_SURFACE=1&lt;/code>&lt;/p>
&lt;/blockquote>
&lt;p>There isn&amp;rsquo;t any information anywhere about this flag, so utilize it with care. From what I have best been able to determine, it forces the application to render layer trees in-process. This will not cause any harm to your system, but it is something that can potentially impact your performance measurements. As a result, it should not be enabled unless debugging is taking place.&lt;/p>
&lt;p>If you&amp;rsquo;re unfamiliar with how to add environmental variables, you can do so in Xcode in &lt;em>Product &amp;gt; Scheme &amp;gt; Edit Scheme&lt;/em>. Select the &lt;em>Run&lt;/em> target, click on the &lt;em>Arguments&lt;/em> tab, and enter the environmental variable in the list near the bottom. It&amp;rsquo;ll look like this:&lt;/p>
&lt;p>&lt;img src="images/env-variables.png" alt="">&lt;/p>
&lt;p>Now that we&amp;rsquo;ve enabled in-process rendering, we can start using other environmental variables to force Core Animation to enable various debugging modes. Let&amp;rsquo;s take a look at some of the most useful ones. More can be found &lt;a href="http://iphonedevwiki.net/index.php/QuartzCore.framework">here&lt;/a>.&lt;/p>
&lt;h3 id="color-opaque">Color Opaque&lt;/h3>
&lt;p>Variable: &lt;code>CA_COLOR_OPAQUE&lt;/code>&lt;/p>
&lt;p>One of the most significant hits to performance can occur as a result of blending. Transparent layers force the GPU to composite layers all the way up the tree until it reaches an opaque layer. This blending can potentially be expensive. Often it is unnecessary, and can be avoided with just a few changes.&lt;/p>
&lt;p>Here&amp;rsquo;s an example. This is a layer that is non-opaque. Notice that it&amp;rsquo;s highlighted in red.&lt;/p>
&lt;p>&lt;img src="images/opaque1.png" alt="">&lt;/p>
&lt;p>By setting it to opaque, it becomes green.&lt;/p>
&lt;p>&lt;img src="images/opaque2.png" alt="">&lt;/p>
&lt;p>In most applications, it&amp;rsquo;s not possible to remove all blending. After all, sometimes it&amp;rsquo;s useful to have blending. However, by removing as much blending as possible, rendering time can be reduced.&lt;/p>
&lt;p>Tips:&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>If the background is a solid color, simply set a background color on the layer that matches that of the layer behind it. This allows your layer to be opaque, but still have the same effect.&lt;/li>
&lt;li>The amount of blending occurring is signified by the shade of overlaied red. A light red color indicates little blending. If removing blending isn&amp;rsquo;t possible, try to make the red color as light as possible.&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;h3 id="color-offscreen">Color Offscreen&lt;/h3>
&lt;p>Variable: &lt;code>CA_COLOR_OFFSCREEN&lt;/code>&lt;/p>
&lt;p>In certain situations, Core Animation is forced to render layers twice: once off-screen and one on-screen. This is simply known as off-screen rendering.&lt;/p>
&lt;p>This type of rendering can be triggered by numerous different situations. Here are some of them:&lt;/p>
&lt;ul>
&lt;li>&lt;code>CALayer&lt;/code> with rasterization enabled (&lt;code>shouldRasterize&lt;/code> = &lt;code>YES&lt;/code>)&lt;/li>
&lt;li>&lt;code>CALayer&lt;/code> with a shadow&lt;/li>
&lt;li>&lt;code>CALayer&lt;/code> with a corner radius&lt;/li>
&lt;li>&lt;code>CALayer&lt;/code> with a mask&lt;/li>
&lt;/ul>
&lt;p>Here&amp;rsquo;s an example. The black square has a shadow applied to it.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objectivec" data-lang="objectivec">&lt;span style="display:flex;">&lt;span>CALayer &lt;span style="color:#f92672">*&lt;/span>layer &lt;span style="color:#f92672">=&lt;/span> [CALayer layer];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>layer.shadowRadius &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">5&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>layer.shadowOpacity &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With the environmental variable enabled, it looks like this:&lt;/p>
&lt;p>&lt;img src="images/offscreen1.png" alt="">&lt;/p>
&lt;p>In order to remove this offscreen rendering pass, we can set a value on the &lt;a href="https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CALayer_class/#//apple_ref/occ/instp/CALayer/shadowPath">&lt;code>shadowPath&lt;/code>&lt;/a> property of our layer. Although the intricacies of &lt;code>shadowPath&lt;/code> our outside of the scope of this article, it essentially bypasses a complete traversal of the layer&amp;rsquo;s sublayers when attempting to figure out the path that the shadow should be cast from. By skipping out on dynamic shadows and providing this ourself, we can usually increase performance quite dramatically. Let&amp;rsquo;s see what setting a shadow path does:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objectivec" data-lang="objectivec">&lt;span style="display:flex;">&lt;span>CGMutablePathRef path &lt;span style="color:#f92672">=&lt;/span> CGPathCreateMutable();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CGPathAddRect(path, NULL, layer.bounds);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>layer.shadowPath &lt;span style="color:#f92672">=&lt;/span> path;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CGPathRelease(path);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="images/offscreen2.png" alt="">&lt;/p>
&lt;p>Much better. On a side note, it seems like if the content view of the window is layer-backed, it is rendered off-screen. I&amp;rsquo;m not exactly sure why.&lt;/p>
&lt;p>Tips:&lt;/p>
&lt;ul>
&lt;li>A common case for setting a border radius on a layer is to get a circle avatar effect. This is rather expensive to do through layers due to off-screen rendering. A good alternative is to pre-render the images clipped to a circle in a drawing context. That circle image can then safely be used as the content for the avatar layer, without it needing any corner radius. Although this has an up-front performance cost, we can avoid the cost per-frame, which is a worthy tradeoff.&lt;/li>
&lt;li>In certain situations, layers can be rasterized (&lt;code>shouldRasterize&lt;/code> = &lt;code>YES&lt;/code>) in order to minimize off-screen rendering. Sometimes this can be relatively beneficial. If the layer&amp;rsquo;s content does not ever change, it is worth the initial cost to rasterize the layer, as layer rendering will be easy. However, if the content of the layer changes frequently, it will be even more costly than if it was disabled.&lt;/li>
&lt;/ul>
&lt;h3 id="color-copied-images">Color Copied Images&lt;/h3>
&lt;p>Variable: &lt;code>CA_COLOR_COPY&lt;/code>&lt;/p>
&lt;p>If you have an application that is image-intensive, minimizing Core Animation image copies is extremely important. Before we discuss how to fix the issue, let&amp;rsquo;s talk about why it occurs.&lt;/p>
&lt;p>Before images can be displayed by Core Animation, they must first be decompressed. Most images are stored on disk in a compressed format, so they must first be decompressed in order to be usable. This is done automatically by Core Animation when it is time to render. This decompression is not a fast process.&lt;/p>
&lt;p>Additionally, even when an image is decompressed it might not be in the right format. Core Animation has a very specific byte-alignment for images, and if the image does not match this, it will be copied &lt;em>on the main thread at render time&lt;/em>. As expected, this is very slow. In an application that requires fast image loading due to scrolling, this can be a huge bottleneck.&lt;/p>
&lt;p>We can detect some of this through the use of the environmental variable shown above. If an image is copied, it might be displayed in blue. Something like this:&lt;/p>
&lt;p>&lt;img src="images/copy1.png" alt="">&lt;/p>
&lt;p>By using proper byte-alignment when decompressing, it should display without any tint:&lt;/p>
&lt;p>&lt;img src="images/copy2.png" alt="">&lt;/p>
&lt;p>Note that this instrument does not seem to always work as expected. Although it&amp;rsquo;s an easy way to check at a glance if there are any issues with image decompression or copying, it&amp;rsquo;s not completely accurate. The most accurate way to detect if copying is occurring is to use Instruments with a time profiler. You&amp;rsquo;ll want to be looking for &lt;code>CA::Render::copy_image&lt;/code> in your stack trace.&lt;/p>
&lt;p>So how can we get proper byte-alignment for our images so Core Animation doesn&amp;rsquo;t need to do a copy? We need to forcibly decompress the images using Core Animation&amp;rsquo;s pixel format by redrawing it in a new context. Here&amp;rsquo;s an example category on &lt;code>NSImage&lt;/code> that can accomplish this. It&amp;rsquo;s recommended to generate this image on a background thread in order to avoid the very problem that we&amp;rsquo;re trying to prevent.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objectivec" data-lang="objectivec">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">@implementation&lt;/span> &lt;span style="color:#a6e22e">NSImage&lt;/span> (JNWDecompression)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> CGContextRef &lt;span style="color:#a6e22e">JNWCreateOpaqueGraphicsContext&lt;/span>(CGSize size) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> size_t width &lt;span style="color:#f92672">=&lt;/span> size.width;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> size_t height &lt;span style="color:#f92672">=&lt;/span> size.height;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> size_t bitsPerComponent &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> size_t bytesPerRow &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span> &lt;span style="color:#f92672">*&lt;/span> width;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGColorSpaceRef colorSpace &lt;span style="color:#f92672">=&lt;/span> NSScreen.mainScreen.colorSpace.CGColorSpace;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (colorSpace &lt;span style="color:#f92672">==&lt;/span> nil) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> colorSpace &lt;span style="color:#f92672">=&lt;/span> (CGColorSpaceRef)CFAutorelease(CGColorSpaceCreateDeviceRGB());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGBitmapInfo bitmapInfo &lt;span style="color:#f92672">=&lt;/span> (CGBitmapInfo)kCGImageAlphaNoneSkipLast;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGContextRef ctx &lt;span style="color:#f92672">=&lt;/span> CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> ctx;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- (NSImage &lt;span style="color:#f92672">*&lt;/span>)&lt;span style="color:#a6e22e">jnw_decompressedImage&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGImageRef CGImage &lt;span style="color:#f92672">=&lt;/span> [self CGImageForProposedRect:nil context:nil hints:nil];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGRect rect &lt;span style="color:#f92672">=&lt;/span> (CGRect){
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .size.width &lt;span style="color:#f92672">=&lt;/span> CGImageGetWidth(CGImage),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .size.height &lt;span style="color:#f92672">=&lt;/span> CGImageGetHeight(CGImage)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (rect.size.width &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">||&lt;/span> rect.size.height &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> nil;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGContextRef context &lt;span style="color:#f92672">=&lt;/span> JNWCreateOpaqueGraphicsContext(rect.size);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGContextDrawImage(context, rect, CGImage);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGImageRef nativeCGImage &lt;span style="color:#f92672">=&lt;/span> CGBitmapContextCreateImage(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NSImage &lt;span style="color:#f92672">*&lt;/span>image &lt;span style="color:#f92672">=&lt;/span> [[NSImage alloc] initWithCGImage:nativeCGImage size:self.size];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGImageRelease(nativeCGImage);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGContextRelease(context);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> image;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">@end&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="ca-instrument">CA Instrument&lt;/h2>
&lt;p>To help make instrumentation easier, I&amp;rsquo;ve put together a small app.&lt;/p>
&lt;p>&lt;img src="images/ca-instrument.png" alt="">&lt;/p>
&lt;p>Here&amp;rsquo;s how to use it:&lt;/p>
&lt;ul>
&lt;li>Select an app to instrument from the drop-down list in the titlebar. You can either select an app manually, or you an select an app that is already running.&lt;/li>
&lt;li>Enable the various instruments that you&amp;rsquo;d like to utilize. It&amp;rsquo;s usually best to only use one at a time.&lt;/li>
&lt;li>Click instrument. The app will be (re)launched with the correct environmental flags enabled.&lt;/li>
&lt;/ul>
&lt;p>The app can be downloaded &lt;a href="%7B%7Bsite.baseurl%7D%7Dapps">here&lt;/a>.&lt;/p></content></item><item><title>A short guide to OS X animations</title><link>https://jwilling.com/blog/osx-animations/</link><pubDate>Mon, 28 Apr 2014 00:00:00 +0000</pubDate><guid>https://jwilling.com/blog/osx-animations/</guid><description>Let&amp;rsquo;s talk about the current state of animations on OS X. We&amp;rsquo;ll go through the best approaches for getting the best performance out of standard animations, and the hacks needed to go beyond basic animations.
No layers The most basic of animations on OS X is driven through the use of NSAnimationContext with no layer-backed views. Here&amp;rsquo;s how we&amp;rsquo;d animate our view to the right:
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) { context.duration = 2.</description><content>&lt;p>Let&amp;rsquo;s talk about the current state of animations on OS X. We&amp;rsquo;ll go through the best approaches for getting the best performance out of standard animations, and the hacks needed to go beyond basic animations.&lt;/p>
&lt;h3 id="no-layers">No layers&lt;/h3>
&lt;p>The most basic of animations on OS X is driven through the use of &lt;code>NSAnimationContext&lt;/code> with no layer-backed views. Here&amp;rsquo;s how we&amp;rsquo;d animate our view to the right:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objectivec" data-lang="objectivec">&lt;span style="display:flex;">&lt;span>[NSAnimationContext runAnimationGroup:&lt;span style="color:#f92672">^&lt;/span>(NSAnimationContext &lt;span style="color:#f92672">*&lt;/span>context) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.duration &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">2.f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> view.animator.frame &lt;span style="color:#f92672">=&lt;/span> CGRectOffset(view.frame, &lt;span style="color:#ae81ff">100&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} completionHandler:nil];
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This is not ideal for many reasons. Since the view is not layer-backed, AppKit will drive this animation on the &lt;em>main thread&lt;/em> by repeatedly calling &lt;code>-setFrame:&lt;/code>, or in this case &lt;code>-setFrameOrigin:&lt;/code>. Animation performance for larger numbers of views using this approach degrades extremely quickly. Your animations will not be smooth. &lt;strong>Don&amp;rsquo;t do this&lt;/strong>.&lt;/p>
&lt;h3 id="layer-backing">Layer-backing&lt;/h3>
&lt;p>So we don&amp;rsquo;t want our animations to be driven on the main thread. What&amp;rsquo;s the alternative? Well we can simply turn on Core Animation by telling our view we want it to have a layer.&lt;/p>
&lt;p>However, this might not be enough. It&amp;rsquo;s extremely important to note that &lt;code>NSView&lt;/code> has a property called &lt;code>layerContentsRedrawPolicy&lt;/code> that dramatically effects the quality of your animations. This property specifies the behavior for view redrawing when animating, and was introduced in 10.6. In OS X 10.7 there were changes under the hood, and the value of the property actually affected how views are animated. &lt;a href="https://developer.apple.com/library/mac/documentation/cocoa/reference/applicationkit/classes/NSView_Class/Reference/NSView.html#//apple_ref/occ/instm/NSView/setLayerContentsRedrawPolicy:">The documentation for this property&lt;/a> is fantastic, and it would be a good idea to give it a quick read before we continue.&lt;/p>
&lt;p>The most important fact to understand about this property is as follows: if your layer contents redraw policy is set to &lt;code>NSViewLayerContentsRedrawDuringViewResize&lt;/code>, &lt;em>which is the default&lt;/em>, your animation performance will likely suffer. In fact, it might even approach the same level of performance you get from not having a layer, as AppKit is still going to call &lt;code>-setFrame:&lt;/code> on your view to drive the animation. Depending on how you want your view to redraw, a good redraw policy to use is likely &lt;code>NSViewLayerContentsRedrawOnSetNeedsDisplay&lt;/code>.
If you set it to the aforementioned redraw policy, the view will be animated using Core Animation, which means the animation will be driven on a dedicated background thread. This will produce smooth animations in most cases.&lt;/p>
&lt;p>Note that some &lt;code>NSView&lt;/code> subclasses (such as &lt;code>NSTextField&lt;/code>) will set the contents policy to the correct redraw policy by default. Check the documentation for more details on whether this is the case, or simply do some introspection on a view in question to see what it&amp;rsquo;s redraw policy is.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objectivec" data-lang="objectivec">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Tell the view to create a backing layer.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>view.wantsLayer &lt;span style="color:#f92672">=&lt;/span> YES;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Set the layer redraw policy. This would be better done in the initialization method of a NSView subclass instead of here.
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>view.layerContentsRedrawPolicy &lt;span style="color:#f92672">=&lt;/span> NSViewLayerContentsRedrawOnSetNeedsDisplay;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[NSAnimationContext runAnimationGroup:&lt;span style="color:#f92672">^&lt;/span>(NSAnimationContext &lt;span style="color:#f92672">*&lt;/span>context) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> context.duration &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">2.f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> view.animator.frame &lt;span style="color:#f92672">=&lt;/span> CGRectOffset(view.frame, &lt;span style="color:#ae81ff">100&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} completionHandler:nil];
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Alternatively, since we now have a layer we can drop down a bit further past implicit animations to interact with Core Animation directly.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objectivec" data-lang="objectivec">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// ... same setup as above
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CGRect frame &lt;span style="color:#f92672">=&lt;/span> CGRectOffset(view.frame, &lt;span style="color:#ae81ff">100&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CABasicAnimation &lt;span style="color:#f92672">*&lt;/span>animation &lt;span style="color:#f92672">=&lt;/span> [CABasicAnimation animationWithKeyPath:&lt;span style="color:#e6db74">@&amp;#34;position&amp;#34;&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>animation.fromValue &lt;span style="color:#f92672">=&lt;/span> [NSValue valueWithPoint:view.frame.origin];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>animation.toValue &lt;span style="color:#f92672">=&lt;/span> [NSValue valueWithPoint:frame.origin];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>animation.duration &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">2.f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[view.layer addAnimation:animation forKey:animation.keyPath];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>view.layer.position &lt;span style="color:#f92672">=&lt;/span> frame.origin;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>However, using Core Animation directly to animate common properties has some &lt;em>significant&lt;/em> issues.&lt;/p>
&lt;h3 id="the-problem-with-layer-backed-views">The problem with layer-backed views&lt;/h3>
&lt;p>If you want to perform animations on the layer beyond setting common &lt;code>NSView&lt;/code> cover methods (such as frame, or frame origin) the situation starts to rapidly escalate into a catastrophe. Apple has specified the following properties as unsafe to modify:&lt;/p>
&lt;blockquote>
&lt;p>&lt;code>geometryFlipped, bounds, frame (implied), position, anchorPoint, transform, shadow*, hidden, filters, and compositingFilter&lt;/code>&lt;/p>
&lt;/blockquote>
&lt;p>Yes, you read that correctly. The frame, bounds, position, anchor point, and &lt;em>transform&lt;/em> properties of a &lt;code>NSView&lt;/code>&amp;rsquo;s backing layer are controlled by the view itself. In other words, the view contains the state of the layer, meaning these properties must be set through the use of the appropriate cover methods on &lt;code>NSView&lt;/code> in order to keep the state in sync. This stands in stark contrast to &lt;code>UIView&lt;/code>, which is simply wraps up much of the state in the layer itself, meaning either the &lt;code>UIView&lt;/code> cover methods or the layer properties themselves can safely be modified.&lt;/p>
&lt;p>Some of the cover methods do exist properly in &lt;code>NSView&lt;/code>, such as the standard frame setter. However, &lt;code>NSView&lt;/code> simply doesn&amp;rsquo;t offer a way to modify the transform! This means that even the smallest attempt at a somewhat complex animation that involves transformations is technically unsupported. It turns out this is one of the largest issues with animations (or even layers) on OS X at the moment and is not easy to solve properly without the use of hacks.&lt;/p>
&lt;h3 id="why-modifying-the-transform-is-difficult">Why modifying the transform is difficult&lt;/h3>
&lt;p>Before we continue, let me state that the &lt;code>transform&lt;/code> property can technically be modified, and the view will properly display with the modified transform. However, there are numerous issues with applying transforms onto views.&lt;/p>
&lt;p>The issue is that the view doesn&amp;rsquo;t consider the transform &lt;em>at all&lt;/em> when it needs to perform any calculations that involve the view state (such as event handling). An example of when this would be an issue is with hit testing. A translation transform that moves a view offscreen will do nothing to the event handling of the view, which means it will still receive clicks and all other events it would normally receive if it was at its original transform.&lt;/p>
&lt;p>Not only does &lt;code>NSView&lt;/code> ignore the transform entirely for state calculations, it also decides to reset the transform every time the view state changes. As of 10.9, there is an internal method, &lt;code>_updateLayerGeometryFromView&lt;/code>, which is called every time the view&amp;rsquo;s state has a change which would affect layer geometry in any way. This method syncs up the current state of the view to the layer, meaning it resets properties that are critical to moderately complex layer modifications, such as &lt;code>transform&lt;/code> or &lt;code>anchorPoint&lt;/code>.&lt;/p>
&lt;p>This resetting of state is understandable, since it is the only way for the view to manage its layer (since the state is kept in the view), but it is unacceptable since it destroys any custom modifications applied to the layer.&lt;/p>
&lt;p>So what can we do?&lt;/p>
&lt;h3 id="how-we-can-hack-custom-layer-transforms">How we can hack custom layer transforms&lt;/h3>
&lt;p>The good news is that we can temporarily work around &lt;code>NSView&lt;/code>&amp;rsquo;s state applications while we have custom transforms applied to a layer. The bad news is that we cannot fix the event handling issues, amongst other issues with the view state. Fixing those issues would require some fairly extreme workarounds.&lt;/p>
&lt;p>&lt;a href="https://gist.github.com/jwilling/8162440">Here&amp;rsquo;s the cleanest approach I could think of to fix this problem&lt;/a>. When a &lt;code>NSView&lt;/code> is told that it needs a layer, it will call &lt;code>-makeBackingLayer&lt;/code> on the view. Since &lt;code>NSView&lt;/code> backing layers use a private class (&lt;code>_NSBackingLayer&lt;/code>), we can&amp;rsquo;t just create a subclass of it, so we need to swizzle a couple of methods on the instance returned by super&amp;rsquo;s implementation of this method. The benefit of approaching it this way is that it is relatively future-proof and shouldn&amp;rsquo;t break even if the class changes in the future. We swizzle &lt;code>-setAnchorPoint:&lt;/code> and &lt;code>-setAffineTransform:&lt;/code>, since these are the two most significant properties that are reset every time the view changes state. We then modify the implementations in our swizzled methods to check a flag before passing through, meaning we can toggle a bypass on and off for both of the setters.&lt;/p>
&lt;p>This approach is still dirty, and I don&amp;rsquo;t want to minimize that. But it does allow for the &lt;em>temporary&lt;/em> setting of an anchor point and a transform for an animation without the fear of &lt;code>NSView&lt;/code> rushing in to ruin the party. Again, do keep in mind that hit testing (amongst other things) will not know anything about the transform you apply.&lt;/p>
&lt;h3 id="why-is-transform-so-important">Why is transform so important?&lt;/h3>
&lt;p>You might be thinking that this ranting about the need for transform animations is overblown and unnecessary. On the contrary, I believe that animating the transform is the &lt;em>one true way&lt;/em> to create outstanding animations. It is an incredibly powerful and performant way to modify many aspects of the visual state of the layer. Especially for animations that bypass Core Animation completely and utilize a display link, calling &lt;code>-setFrame:&lt;/code> on the view will not give decent animation performance due to a significant number of side effects in the setter. Instead, the custom animation can repeatedly call &lt;code>-setTransform:&lt;/code> on the layer and still achieve optimal performance.&lt;/p>
&lt;p>Shiny things like this can be done in just a few lines of code:&lt;/p>
&lt;p>&lt;img src="images/animation.gif" alt="">&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objectivec" data-lang="objectivec">&lt;span style="display:flex;">&lt;span>[IBNSpringInterpolator runInterpolationWithState:state handler:&lt;span style="color:#f92672">^&lt;/span>(CGFloat distance, CGFloat progress) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CGFloat rotation &lt;span style="color:#f92672">=&lt;/span> M_PI &lt;span style="color:#f92672">*&lt;/span> progress;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> view.layer.transform &lt;span style="color:#f92672">=&lt;/span> CATransform3DMakeRotation(rotation, &lt;span style="color:#ae81ff">0.f&lt;/span>, &lt;span style="color:#ae81ff">0.f&lt;/span>, &lt;span style="color:#ae81ff">1.f&lt;/span>);;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> view.layer.position &lt;span style="color:#f92672">=&lt;/span> (CGPoint){ .x &lt;span style="color:#f92672">=&lt;/span> distance, .y &lt;span style="color:#f92672">=&lt;/span> view.frame.origin.y };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} completion:nil];
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And yes, I do plan on opening sourcing that spring interpolator &lt;em>eventually&lt;/em>.&lt;/p>
&lt;h3 id="summing-it-all-up">Summing it all up&lt;/h3>
&lt;p>Here&amp;rsquo;s what you need to know to get great performance:&lt;/p>
&lt;ol>
&lt;li>Use layer-backed views. There is no reason not to.&lt;/li>
&lt;li>Use a layer contents redraw policy of &lt;code>NSViewLayerContentsRedrawOnSetNeedsDisplay&lt;/code> to allow your animations to use Core Animation.&lt;/li>
&lt;li>Don&amp;rsquo;t modify the layer transform unless you know the side effects.&lt;/li>
&lt;li>Custom animations should use the transform if possible for optimal performance.&lt;/li>
&lt;/ol>
&lt;p>Great animations are much more difficult to create on OS X than they are on iOS. They require a significant amount of trial and error, but the results are rewarding.&lt;/p></content></item><item><title>A Core Animation Manifesto</title><link>https://jwilling.com/blog/core-animation-manifesto/</link><pubDate>Tue, 07 May 2013 00:00:00 +0000</pubDate><guid>https://jwilling.com/blog/core-animation-manifesto/</guid><description>Core Animation is an advanced compositing and animation framework for iOS and OS X. Not only does it open up the ability to perform incredible animations with just a few lines of code, but it also includes powerful objects called layers, which are extremely lightweight objects that contain some type of visual content which can be easily manipulated by transforms. This article will explore various aspects of Core Animation, beginning with layers.</description><content>&lt;p>Core Animation is an advanced compositing and animation framework for iOS and OS X. Not only does it open up the ability to perform incredible animations with just a few lines of code, but it also includes powerful objects called layers, which are extremely lightweight objects that contain some type of visual content which can be easily manipulated by transforms. This article will explore various aspects of Core Animation, beginning with layers.&lt;/p>
&lt;p>Core Animation was initially named Layer Kit. What are layers? Layers are a lightweight data structures that are similar to views. They can be arranged in a hierarchy to create a user interface. The parent layer (superlayer) can contain a set of layers (sublayers), forming a layer tree. The sublayers have a coordinate system that is relative to their superlayer.&lt;/p>
&lt;p>The real power of layers is their ability to be transformed by transform matrices. Although layers are primarily designed for 2D interfaces, they do exist in a 3D coordinate system and can therefore be transformed in three dimensions. Each instance of a layer has its own coordinate system. Any sublayers added to a layer will be positioned relative to that layer. Layer coordinate systems actually differ between platforms. On iOS, the origin is the top-left corner, whereas on OS X the origin is the bottom-left corner. The remainder of this article will use the OS X coordinate system.&lt;/p>
&lt;p>Layers contain multiple properties which affect their geometry. The first is the position. The position is just a struct of type &lt;code>CGPoint&lt;/code>, which contains an x and y coordinate, which are relative to the origin of the superlayer. The bounds of a layer is a combination of a struct containing the size of a layer (&lt;code>CGSize&lt;/code>), and the origin of the graphics context, which is usually {0, 0}. The anchorPoint is a &lt;code>CGPoint&lt;/code> that has valid values in the range of 0.0 – 1.0. The anchor point directly affects how the position of the layer relates to the bounds. For example, a layer with an anchor point of {0.5, 0.5} will have its position set relative to the exact center of the layer. The anchor point also affects transforms, which will be discussed later. Layers also have a frame property, which is derived from the anchor point, position, and bounds.&lt;/p>
&lt;p>Layers can be transformed using transform matrices. These transforms can be represented as &lt;code>CATransform3D&lt;/code> structs. &lt;code>CATransform3D&lt;/code> structs merely contain a 4x4 matrix of floats that represent a transform matrix. By default, a layer has its transform property set to &lt;code>CATransform3DIdentity&lt;/code>, which unsurprisingly is the identity matrix. Applying this identity matrix to the layer will revert any transforms applied, and will reset the layer to its default position, scale, rotation, and perspective. There are many ways to transform a layer. Although a transform matrix can be created manually, it is easier to use the built-in functions to easily create a transform with a specific purpose. &lt;code>CATransform3DMakeTranslation&lt;/code> creates a transform matrix which is translated by x, y, and z. &lt;code>CATransform3DMakeScale&lt;/code> returns an identity matrix scaled by x, y, and z. &lt;code>CATransform3DMakeRotation&lt;/code> is a transform matrix which rotates by x, y, and z in radians. A complex transform matrix can be formed by combining these transforms into one matrix. This can be accomplished by using &lt;code>CATransform3DConcat&lt;/code>, which takes in two transform matrices and concatenates the second onto the first (a * b). Alternatively, variants of the transformation functions can be used to directly apply one transform onto an existing transform. These are &lt;code>CATransform3DTranslate&lt;/code>, &lt;code>CATransform3DScale&lt;/code>, and &lt;code>CATransform3DRotate&lt;/code>, which take in the same arguments as their counterparts, with the addition of a pre-existing transform. These transforms can be used to create some stunning effects, and are most useful when combined with animation, which will be discussed in a later section.&lt;/p>
&lt;p>A layer can be provided with content in multiple ways. The first and easiest method is to simply set the contents property of the layer to an image. The image can either be of type &lt;code>NSImage&lt;/code> on the Mac, or &lt;code>CGImageRef&lt;/code> on iOS and Mac. The current backing store of the layer is replaced with the content of the image. This behavior can be useful when focusing on optimization, and will be explored in further detail later. Another way to provide content to the layer is through drawing. Layers provide access to the drawing context through the delegate method &lt;code>-drawLayer:inContext&lt;/code>:, and the overridden &lt;code>-drawInContext:&lt;/code> method for subclasses. In either case, the context is provided as a &lt;code>CGContextRef&lt;/code>, which can be used to draw graphics as desired. The drawing is then performed on the CPU, then the context is converted into a texture optimized for the GPU. Behind the scenes, Core Animation ties in directly with Open GL ES. Layers are really just wrappers around GPU textures, because the content is rasterized into a texture before being displayed on the screen.&lt;/p>
&lt;p>Core Animation has multiple types of layers built-in. The main class, &lt;code>CALayer&lt;/code>, is the parent class for all other types of layers. It provides all the basic features that layers need. Core Animation also includes specialized subclasses of &lt;code>CALayer&lt;/code> that add on additional features, or modify behavior in some cases. &lt;code>CATiledLayer&lt;/code> is designed to display content with massive dimensions that would be impractical and impossible to load normally. As its name implies, &lt;code>CATiledLayer&lt;/code> splits up its content into smaller tiles. As transforms are applied to zoom the layer, tiles are dropped and redrawn at the requested detail asynchronously. &lt;code>CAScrollLayer&lt;/code> is optimized for scrolling and displaying a portion of a layer. &lt;code>CATextLayer&lt;/code> makes it easy to create a layer with a content of text. There are also additional classes that are only available for specific platforms. On OS X there are &lt;code>CAOpenGLLayer&lt;/code>, &lt;code>QCCompositionLayer&lt;/code>, &lt;code>QTMovieLayer&lt;/code>, and &lt;code>QTCaptureLayer&lt;/code>, whereas on iOS there is &lt;code>CAEAGLLayer&lt;/code>.&lt;/p>
&lt;p>Although layers by themselves are incredibly useful for creating performant interfaces, they are complemented with a powerful ability to tie in with the animation aspect of Core Animation. Apple’s implementation of animations is fantastic. On a superficial level, animations can be performed by creating an instance of an animation and adding it to a layer. Once an animation is added, Core Animation handles the rest. A dedicated secondary thread is used by Core Animation to ensure that regardless of whether the GPU can keep up with a full 60 frames per second, the animation will finish precisely on time. If needed, Core Animation will drop frames automatically to guarantee this behavior.&lt;/p>
&lt;p>A brief aside must be taken to discuss the basics of a technology in the Objective-C language known as Key-Value Coding, or KVC. KVC is a method of accessing a property on an object through the use of strings, instead of directly accessing the object’s instance variables or accessors. At first this might seem useless, but in reality it can be incredibly useful, especially when combined with Core Animation. Accessing a KVC-compliant property using KVC is as simple as calling &lt;code>-valueForKey:&lt;/code>, passing in the name of the property as a &lt;code>NSString&lt;/code>. Use of this technology can dramatically simplify code, such as in the case where identifiers are mapped to property names. Another way of accessing values is through the use of key paths. A key path is a string that in essence “drills down” through KVC-compliant properties until the desired value has been found. Specific Core Animation classes, namely &lt;code>CAAnimation&lt;/code> and &lt;code>CALayer&lt;/code> extend the &lt;code>NSKeyValueCoding&lt;/code> protocol, and add support for key paths, even for structs such as &lt;code>CGPoint&lt;/code> and &lt;code>CATransform3D&lt;/code>. This ability allows for Core Animation to provide an incredibly simple API for animating specific values.&lt;/p>
&lt;p>To get started with a basic animation, Core Animation includes a standard object, &lt;code>CABasicAnimation&lt;/code>, that provides all the necessary tools to create an animation which interpolates from one value to another. It is a subclass of CAPropertyAnimation, which in turn is a subclass of &lt;code>CAAnimation&lt;/code>. The first step is to create an instance of &lt;code>CABasicAnimation&lt;/code>, which can be done by using the designated property initializer, &lt;code>+animationWithKeyPath:&lt;/code>. Alternatively, the standard &lt;code>CAAnimation&lt;/code> initializer can be used by calling &lt;code>+animation&lt;/code> and setting the &lt;code>keyPath&lt;/code> property afterwards. The key path is the property or value which needs to be animated. Not all of &lt;code>CALayer&lt;/code>’s properties are animatable, but those that aren’t are explicitly listed in Apple’s documentation. Next, the animation needs to know what you want to interpolate from, and what you want to interpolate to. This can be done by setting the &lt;code>fromValue&lt;/code> and &lt;code>toValue&lt;/code>, which are both of type &lt;code>id&lt;/code>. As a consequence of the type being id, scalar types must be wrapped into objects. Floats and other scalar numbers can be wrapped into &lt;code>NSNumber&lt;/code>s. Other structs such as &lt;code>CGRect&lt;/code> can be wrapped into &lt;code>NSValue&lt;/code>s. For example, suppose you would like to animate the position. The position in this case would be a &lt;code>CGPoint&lt;/code>, which is a struct. We need to wrap this in a &lt;code>NSValue&lt;/code>, which can be performed by calling &lt;code>NSValue&lt;/code>’s &lt;code>+valueWithCGPoint:&lt;/code> method. The final property worth mentioning in &lt;code>CABasicAnimation&lt;/code> is &lt;code>byValue&lt;/code>, which is optional. If set, Core Animation will attempt to use the value as an increment between interpolation steps. The other inherited properties (such as duration) are optional as well, and will be set to their default values if omitted.&lt;/p>
&lt;p>Somewhat more complex animations can be formed by using &lt;code>CAKeyframeAnimation&lt;/code>. Keyframe animations in Core Animation seem simple at a first glance, but they can be used to create some surprisingly complex animations. Instead of taking control over the entire interpolation as &lt;code>CABasicAnimation&lt;/code> does, &lt;code>CAKeyframeAnimation&lt;/code> allows manipulation of intermediate points along the interpolation, which allows for far more control over the animation. There is only one property which is required to be set before the animation is able to be used, namely the values array, which is of type &lt;code>NSArray&lt;/code>. The array of values needs to contain at minimum the ending value, but it may contain as many values as required to create the specific animation. There is also an optional property, &lt;code>keyTimes&lt;/code>, which is an array of numbers ranging from 0 to 1 which correspond directly to their related values in the values array. If the count of both arrays is not identical, or if invalid numbers are specified in &lt;code>keyTimes&lt;/code>, it will be ignored. Since the times range from 0 to 1, the numbers could be thought of as percentages of the total duration of the animation. If the animation needs to move along a specific path, &lt;code>CAKeyframeAnimation&lt;/code> has an optional property that accepts a &lt;code>CGPathRef&lt;/code>. This path will take precedence over the values array (if provided), and will be used along with the key times (if set) to create a motion along the path. There are several other properties on &lt;code>CAKeyframeAnimation&lt;/code> that can be used to further modify the behavior of the animation, but they are outside the scope of this article. However, there is one property, &lt;code>timingFunctions&lt;/code>, which serves a unique purpose of allowing for different timing functions for each of the values in the animation.&lt;/p>
&lt;p>The ability to set timing functions is also present in &lt;code>CABasicAnimation&lt;/code>. By default, animations will use a mostly linear curve, meaning the object will move from one value to another without much variance in the speed. For some purposes this can be useful, but when creating user interface animations this can make the animations appear &amp;ldquo;dead&amp;rdquo;. This can be easily remedied by using the &lt;code>timingFunction&lt;/code> property declared in &lt;code>CAAnimation&lt;/code>, from which &lt;code>CABasicAnimation&lt;/code> inherits. In the case of &lt;code>CAKeyframeAnimation&lt;/code>, the timing function can be set through the the related array of &lt;code>timingFunctions&lt;/code>. Both cases require an object of type &lt;code>CAMediaTimingFunction&lt;/code>. A timing function object is usually created by using &lt;code>+functionWithName:&lt;/code>, passing in one of the timing function constants. These constants are &lt;code>kCAMediaTimingFunctionLinear&lt;/code>, &lt;code>kCAMediaTimingFunctionEaseIn&lt;/code>, &lt;code>kCAMediaTimingFunctionEaseOut&lt;/code>, &lt;code>kCAMediaTimingFunctionEaseInEaseOut&lt;/code>, and &lt;code>kCAMediaTimingFunctionDefault&lt;/code>. For most purposes &lt;code>kCAMediaTimingFunctionEaseInEaseOut&lt;/code> provides the most natural feeling for animations. Custom timing functions can also be created by using the &lt;code>-initWithControlPoints::::&lt;/code> method on &lt;code>CAMediaTimingFunction&lt;/code>. Although this offers a good degree of flexibility, the shape of the resulting bezier path is still cubic, and multiple points are unable to be specified. However, &lt;code>CAKeyframeAnimation&lt;/code> was designed with this in mind, and can be an excellent substitute if the timing functions do not fit the needed style of animation.&lt;/p>
&lt;p>Although layers can be extremely performant, there are some bottlenecks that can cause unwanted lag. The first area of optimization is blending. When a layer does not have any transparent areas it is opaque. Opaque layers are not blended with any layers beneath them. When a layer is non-opaque and is placed above another layer, the GPU is forced to blend the two layers together, which is quite an expensive operation. Using the Core Animation instrument (currently only available for iOS), detecting blended layers is as easy as checking the box that is labeled “Color Blended Layers”. The areas that are in red are blended, and should be reduced as much as possible to avoid multiple layout passes.&lt;/p>
&lt;p>The second area of optimization is pixel alignment. Core Animation, UIKit, and AppKit all use floating-point numbers to store pixel coordinates. When the content is actually drawn on the screen, the pixels don’t align with the points. This causes anti-aliasing, which can put some degree of strain on the GPU, not to mention the unsightly fuzzy appearance. This issue can be resolved easily by ensuring that coordinate calculations are always truncated or rounded to the nearest integer. Since non-integral coordinates usually occur when layout calculations take place, the recommended approach is to use the &lt;code>floor()&lt;/code> or &lt;code>ceil()&lt;/code> functions to truncate or round the numbers.&lt;/p>
&lt;p>Finally, a large source of performance issues can arise from masking to bounds. If a layer has a corner radius set, for example, masking to bounds must be turned on for the layer to render with the rounded corners. Unfortunately, turning on masking actually causes the layer to perform multiple offscreen render passes, which can absolutely destroy performance. There are multiple ways to work around this issue, but the simplest and arguably the best way to avoid this problem is to avoid masking at all. The corner radius effect can be emulated by clipping the context to a path before drawing. Another way to work around this issue is to rasterize the layer.&lt;/p>
&lt;p>Layer rasterizing can sometimes bring fantastic performance boosts to complex layers. Layer rasterization can be turned on by setting the &lt;code>shouldRasterize&lt;/code> property on the &lt;code>CALayer&lt;/code> in question to YES. Rasterization works by rendering the entire layer as a bitmap, including effects such as masked bounds and shadows. This can potentially cause a tremendous increase in speed when animating. The GPU excels at moving around textures, and rasterized layers are no exception. However, rasterizing layers does not always work in your favor. If the content of the layer changes at all, the entire bitmap cache of the layer is invalidated, rendered, and cached again. Naturally this is quite inefficient, and as a result layers that will have any changes applied to them during animation are advised not to rasterize. Additionally, just because a layer is rasterized does not mean that non-opaque layers with blending issues will have improved performance. The blending will still need to take place. Finally, rasterization requires more memory usage due to the additional bitmap cache of the layer. These allocations are significantly more expensive due to the fact that they are effectively mutable surface textures. They will be paged to another backing store rather than destroyed for resource-constrained scenarios. On OS X this is not much of a worry due to the large amount of memory available to applications, but on iOS devices memory usage can be a potential concern.&lt;/p>
&lt;p>Decreasing memory usage is a vital aspect of development. Although layers are lightweight, they still must keep their textures in memory. If the same texture is used repeatedly, the same bitmap can be shared across multiple layers. Suppose a table view had cells that contained the same background for each of them. One way to optimize this would be to draw the background once, and render that background into an image that all the cells have access to. Then, each cell can either set their layer’s contents to this image, or create sublayers using the same image for their content. The end result is the reuse of the single bitmap, which helps to bring some welcome memory improvements.&lt;/p>
&lt;p>Animations are fantastic. However, they serve a purpose. An application that includes animations just for the sake of having animations should not have them at all. Animations serve multiple purposes, but their most important purpose is to provide context. Suppose you’re using an email client. You are scrolling through your emails, and you decide you no longer need one of them. You tap or click delete. Without animation serving as contextual guidance, the cell that contained the information about the email you currently selected suddenly disappears. This happens so quickly that your eye notices a flash, and suddenly the list is updated and the old email is nowhere to be found. At this point, you might second-guess yourself, and start to wonder whether you really deleted the right message. This type of behavior does not happen in real life, and therefore it’s hard to relate to it. Now suppose this whole process is animated. You delete the message, and it gently drifts away and fades out, while the messages beneath it slide up to fill the gap left by the deleted message. This action is much more relatable to in real life, and an animation in this case gives the user reassurance that the correct message was indeed deleted.&lt;/p>
&lt;p>As it unsurprisingly turns out, interfaces that implement these type of natural animations are far more intuitive than those that do not. On the other hand, interfaces that implement animations that do not exist in real life are quite counterintuitive. Another example of an intuitive animation is on iOS. When navigating to a new level in a view hierarchy, the old view is pushed out to the left while the new view is simultaneously pushed in from the right. A similar situation could be created in the real world if two papers were taped together and put underneath a frame that only showed one single paper. If the paper under the frame were pushed to the left, the paper attached to the right would slide into view. These type of animations and movements are instantly relatable; no explanation is needed. This is how animation should be.&lt;/p>
&lt;p>Core Animation is truly a masterpiece of engineering. A short amount of code can produce stunning animations that enhance your interface. Nearly all the complicated work previously needed to create complex transformations are completely unnecessary with Core Animation. With some amount of care, proper use of Core Animation can transform your application into something extraordinary.&lt;/p></content></item><item><title>Optimized Scrolling in NSTableView</title><link>https://jwilling.com/blog/optimized-nstableview-scrolling/</link><pubDate>Fri, 11 Jan 2013 00:00:00 +0000</pubDate><guid>https://jwilling.com/blog/optimized-nstableview-scrolling/</guid><description>First introduced in OS X Lion, view-based table views are a significant advancement over previous cell-based table views. Not only are views easier to work with and customize, but they&amp;rsquo;re also trivial to animate. The API is fantastic. So what&amp;rsquo;s the catch?
Scrolling. A standard, unoptimized view-based table view with a somewhat complex layout will have an extremely tough time achieving buttery smooth scrolling at 60 fps. The good news is that we can fix this.</description><content>&lt;p>First introduced in OS X Lion, view-based table views are a significant advancement over previous cell-based table views. Not only are views easier to work with and customize, but they&amp;rsquo;re also trivial to animate. The API is fantastic. So what&amp;rsquo;s the catch?&lt;/p>
&lt;p>Scrolling. A standard, unoptimized view-based table view with a somewhat complex layout will have an extremely tough time achieving buttery smooth scrolling at 60 fps. The good news is that we can fix this. Ready? Let&amp;rsquo;s get started.&lt;/p>
&lt;h3 id="its-all-about-the-layers">It&amp;rsquo;s all about the layers&lt;/h3>
&lt;p>Views without layers require a redraw after every frame change. Layer-backed views, on the other hand, are able to cache the result of drawing into an bitmap cached in the GPU, which can then animate and transform that bitmap with great ease. &lt;code>NSView&lt;/code> does not enable layer-backing by default.&lt;/p>
&lt;p>One class that is critical in the process of scrolling is &lt;code>NSClipView&lt;/code>. A clip view is used to contain the document view of a &lt;code>NSScrollView&lt;/code>, clip the document view to its frame, and update the &lt;code>NSScrollView&lt;/code> when the document view&amp;rsquo;s size or position changes&lt;!-- raw HTML omitted -->&lt;a href="https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSClipView_Class/Reference/Reference.html">1&lt;/a>&lt;!-- raw HTML omitted -->.&lt;/p>
&lt;hr>
&lt;p>&lt;strong>Update 09/22/15&lt;/strong>: The information below was written for OS X 10.7. Scroll views as of 10.8+ seem to perform well without needing to customize the layer class of the clip view. I would recommend against swapping the clip view. Rather, the containing scroll view should have &lt;code>wantsLayer&lt;/code> set to &lt;code>YES&lt;/code>.&lt;/p>
&lt;hr>
&lt;p>If your window&amp;rsquo;s content view, or any superview of the clip view contains a layer, it will implicitly get a layer generated for itself as well. By default it will use a &lt;code>_NSViewBackingLayer&lt;/code> for the backing layer. Unfortunately, this type of layer isn&amp;rsquo;t quite ideal for a clip view. A much more ideal subclass of &lt;code>CALayer&lt;/code> is &lt;code>CAScrollLayer&lt;/code>. A &lt;a href="http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CAScrollLayer_class/Introduction/Introduction.html">&lt;code>CAScrollLayer&lt;/code>&lt;/a>, as its name implies, is a subclass of &lt;code>CALayer&lt;/code> that is optimized for scrolling, and displaying a portion of a layer. Wouldn&amp;rsquo;t it be great if we could use this for our &lt;code>NSClipView&lt;/code>? Well, we can.&lt;/p>
&lt;p>Telling a subclass of &lt;code>NSClipView&lt;/code> to use a &lt;code>CAScrollLayer&lt;/code> is an easy matter of &lt;strong>overriding the designated initializer, setting the layer to a &lt;code>CAScrollLayer&lt;/code>, and telling the clip view to use a layer&lt;/strong>. Since we create and set the layer before telling the clip view it needs a layer, this is a layer-hosting view.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objectivec" data-lang="objectivec">&lt;span style="display:flex;">&lt;span>- (&lt;span style="color:#66d9ef">id&lt;/span>)&lt;span style="color:#a6e22e">initWithFrame:&lt;/span>(NSRect)frame {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self &lt;span style="color:#f92672">=&lt;/span> [super initWithFrame:frame];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>self) &lt;span style="color:#66d9ef">return&lt;/span> nil;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self.layer &lt;span style="color:#f92672">=&lt;/span> [CAScrollLayer layer];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self.wantsLayer &lt;span style="color:#f92672">=&lt;/span> YES;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self.layerContentsRedrawPolicy &lt;span style="color:#f92672">=&lt;/span> NSViewLayerContentsRedrawNever;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> self;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>We&amp;rsquo;re not quite there yet. In order to use this fancy clip view, we&amp;rsquo;re going to need to &lt;strong>replace the existing clip view with our custom clip view at runtime&lt;/strong>. There are multiple ways to do this. I chose to subclass &lt;code>NSScrollView&lt;/code> and swap the clip view out during initialization.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-objectivec" data-lang="objectivec">&lt;span style="display:flex;">&lt;span>- (&lt;span style="color:#66d9ef">id&lt;/span>)&lt;span style="color:#a6e22e">initWithFrame:&lt;/span>(NSRect)frameRect {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self &lt;span style="color:#f92672">=&lt;/span> [super initWithFrame:frameRect];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (self &lt;span style="color:#f92672">==&lt;/span> nil) &lt;span style="color:#66d9ef">return&lt;/span> nil;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [self swapClipView];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> self;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- (&lt;span style="color:#66d9ef">void&lt;/span>)&lt;span style="color:#a6e22e">awakeFromNib&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [super awakeFromNib];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>[self.contentView isKindOfClass:CustomClipView.&lt;span style="color:#66d9ef">class&lt;/span>] ) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [self swapClipView];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- (&lt;span style="color:#66d9ef">void&lt;/span>)&lt;span style="color:#a6e22e">swapClipView&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self.wantsLayer &lt;span style="color:#f92672">=&lt;/span> YES;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">id&lt;/span> documentView &lt;span style="color:#f92672">=&lt;/span> self.documentView;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CustomClipView &lt;span style="color:#f92672">*&lt;/span>clipView &lt;span style="color:#f92672">=&lt;/span> [[CustomClipView alloc] initWithFrame:self.contentView.frame];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self.contentView &lt;span style="color:#f92672">=&lt;/span> clipView;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> self.documentView &lt;span style="color:#f92672">=&lt;/span> documentView;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The layer-backed clip view already exists in Github&amp;rsquo;s &lt;a href="https://github.com/github/rebel">Rebel&lt;/a>, and I&amp;rsquo;ve added the custom scroll view to Rebel as well. I highly recommend using it.&lt;/p>
&lt;p>Ideally, scrolling should be quite a bit better now. But we&amp;rsquo;re not done yet.&lt;/p>
&lt;h3 id="auto-layout">Auto Layout&lt;/h3>
&lt;p>The idea behind Auto Layout is fantastic. However, with view-based table cells that require somewhat complex layout, auto layout has the potential to cause quite a bit of lag. The only way to remove all auto layout from the table view is to disable auto layout &lt;em>for your entire window.&lt;/em> Yes, it&amp;rsquo;s not ideal. But once auto layout is triggered for any view in your window, the layout system is enabled for every other view in the window, including your table view.&lt;/p>
&lt;p>Now let me reiterate: this is only necessary for cells that require a significant amount of layout customization. If you just have an image and a couple of labels, this might not be necessary. &lt;strong>Profiling is the only way to determine for sure if auto layout is your problem.&lt;/strong>&lt;/p>
&lt;h3 id="drawing">Drawing&lt;/h3>
&lt;p>If you want a custom cell appearance, the recommended way to approach this is to subclass &lt;code>NSTableRowView&lt;/code>, override one of the drawing methods (such as &lt;code>-drawBackgroundInRect:&lt;/code>), and draw the custom appearance.&lt;/p>
&lt;p>When a table cell view (or a table row view) is dequeued, it is removed from its current superview, and re-added once it has been appropriately reset and enqueued. This means that while scrolling your table view, &lt;code>-drawBackgroundInRect:&lt;/code> is being called repeatedly every time the row view is dequeued.&lt;/p>
&lt;p>A better approach would be to &lt;strong>cache your background as a stretchable image&lt;/strong>. This image can then be simply set to the layer&amp;rsquo;s contents on your row view or cell view. By reusing the same image, you&amp;rsquo;re using the same bitmap in memory for all the rows, and you&amp;rsquo;re avoiding expensive drawing of your background.&lt;/p>
&lt;p>If you&amp;rsquo;re using 10.8, the most optimized way to do this is to override &lt;code>-wantsLayer&lt;/code> and return &lt;code>YES&lt;/code>, which allows AppKit in turn to call &lt;code>-updateLayer&lt;/code>. This is the recommended time to set the layer&amp;rsquo;s contents.&lt;/p>
&lt;h3 id="what-else">What else?&lt;/h3>
&lt;p>Listed above are just a few techniques I&amp;rsquo;ve discovered that help make scrolling more smooth. In a future post I&amp;rsquo;ll detail how scrolling using the arrow keys can be recreated to provide a smoother animation.&lt;/p></content></item><item><title>About</title><link>https://jwilling.com/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jwilling.com/about/</guid><description>I&amp;rsquo;m a developer and designer. I&amp;rsquo;m passionate about crafting beautiful apps, building performant animations, and creating delightful UI.
Catch me on Twitter, Mastodon, or check out my GitHub.</description><content>&lt;p>I&amp;rsquo;m a developer and designer. I&amp;rsquo;m passionate about crafting beautiful apps, building performant animations, and creating delightful UI.&lt;/p>
&lt;p>Catch me on &lt;a href="https://twitter.com/willing">Twitter&lt;/a>, &lt;a href="https://mastodon.social/@willing">Mastodon&lt;/a>, or check out my &lt;a href="http://github.com/jwilling">GitHub&lt;/a>.&lt;/p></content></item></channel></rss>