<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Peter Iakovlev</title>
    <description>The latest articles on DEV Community by Peter Iakovlev (@petertech).</description>
    <link>https://dev.to/petertech</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1085639%2Fa809cc8d-2ef6-4e9f-bc4b-124ad97da8fe.png</url>
      <title>DEV Community: Peter Iakovlev</title>
      <link>https://dev.to/petertech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/petertech"/>
    <language>en</language>
    <item>
      <title>Reducing JPEG UIImage RAM Usage by 50%</title>
      <dc:creator>Peter Iakovlev</dc:creator>
      <pubDate>Sun, 30 Jul 2023 19:48:12 +0000</pubDate>
      <link>https://dev.to/petertech/reducing-jpeg-uiimage-ram-usage-by-50-2jed</link>
      <guid>https://dev.to/petertech/reducing-jpeg-uiimage-ram-usage-by-50-2jed</guid>
      <description>&lt;p&gt;In 2013, Apple shifted from the detailed, texture-rich designs of the past to a simpler style filled with basic shapes and vector icons. But even in this modern era of straight lines and plain gradients, raster graphics are still not obsolete. This is particularly true on iOS, where there's limited support for fast vector shape rendering. The most effective way to draw complex user interfaces is by turning static parts into simple images, an area where the iOS compositor excels. In addition to UI elements, raster graphics also play an indispensable role in non-UI elements, such as photos, which can't be adequately represented using vector graphics alone.&lt;/p&gt;

&lt;p&gt;Rendering raster images can greatly impact the device's memory in non-obvious ways. This is because the memory used by an image is decided by the number of its pixels, not its file size. In this article, we'll exploit one of the more obscure techniques to render such images on Apple platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  RGB vs YUV
&lt;/h2&gt;

&lt;p&gt;In iOS, we typically work with raster images represented as an array of ARGB values (where A stands for alpha) for each pixel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="c1"&gt;//  a     r     g     b&lt;/span&gt;
    &lt;span class="mh"&gt;0xff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this representation, one pixel requires exactly 4 bytes of storage. To put this in perspective, an iPhone 14 Pro Max with a resolution of 2796 x 1290, would need to allocate 2796 * 1290 * 4 = 14427360 bytes (or 14.4 megabytes) of RAM to fill the entire screen with pixels. For comparison, an average JPEG photo of the same size would occupy around 1 megabyte of disk space.&lt;/p&gt;

&lt;p&gt;JPEGs use a number of tricks to reduce the size of image data, with one of the first being a clever re-organization of colors known as YUV. RGB data can be losslessly converted into the YUV representation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;rgbToYuv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;luminance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.299&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.587&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.114&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1.772&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;luminance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1.402&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;luminance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;luminance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;yuvToRgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.402&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.299&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.402&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;0.587&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.114&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.772&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;0.587&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.772&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="nf"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the YUV representation, the first component (Y) represents the brightness of a pixel, while the other two encode color.&lt;/p&gt;

&lt;p&gt;Simply converting our RGB data into the YUV format will not result in any data storage savings, however. To actually compress the data, we'll need to discard some information, making the color transformation lossy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chroma Subsampling
&lt;/h2&gt;

&lt;p&gt;The human eye is quite sensitive when it comes to variations in pixel brightness but is less perceptive of color. It turns out we can store color data at half the resolution and not lose much in the way of visual clarity, especially at higher resolutions and with natural, photographic images. This is known as 4:2:0 subsampling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfk44hni1pa3a542s5dx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfk44hni1pa3a542s5dx.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above illustrates the extreme scenario where chroma subsampling is visually apparent — where two bright-colored, contrasting objects overlap. In naturally occurring images, the effect is much less pronounced.&lt;/p&gt;

&lt;p&gt;The 4:2:0-sampled image requires 50% less bytes of storage, as every four YUV pixels share the same color value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 RGB pixels = 4 * (4 * 3) = 12 bytes
4 YUV pixels = 4 * 1 + 1 + 1 = 6 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  UIImage and YUV
&lt;/h2&gt;

&lt;p&gt;Unfortunately, iOS does not provide any APIs that would allow us to store uncompressed YUV 4:2:0 data in a &lt;code&gt;UIImage&lt;/code&gt;. However, there are a couple of ways to work around this.&lt;/p&gt;

&lt;p&gt;First, it is possible to use an &lt;code&gt;AVSampleBufferDisplayLayer&lt;/code&gt; to display static image content. &lt;code&gt;AVSampleBufferDisplayLayer&lt;/code&gt; can display ARGB content, but to achieve RAM space savings, we will convert the image to YUV format using &lt;code&gt;vImage&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;imageToYUVCVPixelBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;CVPixelBuffer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preparingForDisplay&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;cgImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cgImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataProvider&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CFDataGetBytePtr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;colorSpace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cgImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colorSpace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;colorSpace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cgImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bitsPerPixel&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;cgImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bitsPerComponent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cgImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cgImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;

    &lt;span class="c1"&gt;// 2&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CVPixelBuffer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CVPixelBufferCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kCFAllocatorDefault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kCVPixelFormatType_420YpCbCr8BiPlanarFullRange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nv"&gt;kCVPixelBufferIOSurfacePropertiesKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSDictionary&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFDictionary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pixelBuffer&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;CVPixelBufferLockBaseAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CVPixelBufferLockFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;CVPixelBufferUnlockBaseAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CVPixelBufferLockFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;baseAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CVPixelBufferGetBaseAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assumingMemoryBound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CVPlanarPixelBufferInfo_YCbCrBiPlanar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 3&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;pixelRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vImage_YpCbCrPixelRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Yp_bias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CbCr_bias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;YpRangeMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CbCrRangeMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;YpMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;YpMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CbCrMax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CbCrMin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vImage_ARGBToYpCbCr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;vImageConvert_ARGBToYpCbCr_GenerateConversion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kvImage_ARGBToYpCbCrMatrix_ITU_R_709_2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pixelRange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kvImageARGB8888&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kvImage420Yp8_Cb8_Cr8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;vImage_Flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kvImageDoNotTile&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;kvImageNoError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 4&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;srcBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vImage_Buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UnsafeMutableRawPointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mutating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;vImagePixelCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;vImagePixelCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;rowBytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cgImage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bytesPerRow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;dstBufferY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vImage_Buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UnsafeMutableRawPointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mutating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;baseAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;advanced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CFSwapInt32BigToHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseAddress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pointee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;componentInfoY&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)))),&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;vImagePixelCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;vImagePixelCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;rowBytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CFSwapInt32BigToHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseAddress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pointee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;componentInfoY&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowBytes&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;dstBufferCbCr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vImage_Buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UnsafeMutableRawPointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;mutating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;baseAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;advanced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CFSwapInt32BigToHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UInt32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseAddress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pointee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;componentInfoCbCr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)))),&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;vImagePixelCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;vImagePixelCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;rowBytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CFSwapInt32BigToHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseAddress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pointee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;componentInfoCbCr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowBytes&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="c1"&gt;// 5&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;permuteMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;vImageConvert_ARGB8888To420Yp8_CbCr8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;srcBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dstBufferY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dstBufferCbCr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;permuteMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;vImage_Flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kvImageDoNotTile&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;kvImageNoError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pixelBuffer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1 - Ensure the source image is fully loaded into memory and in the appropriate format.&lt;br&gt;
2 - Create a CVImagePixelBuffer that will store data in the &lt;code&gt;kCVPixelFormatType_420YpCbCr8BiPlanarFullRange&lt;/code&gt; format.&lt;br&gt;
3 - Prepare vImage for the RGB to YUV conversion.&lt;br&gt;
4 - Populate the source and destination vImage buffer structures.&lt;br&gt;
5 - Execute the actual conversion.&lt;/p&gt;

&lt;p&gt;To display a &lt;code&gt;CVPixelBuffer&lt;/code&gt; within an &lt;code&gt;AVSampleBufferDisplayLayer&lt;/code&gt;, it needs to be encapsulated in a &lt;code&gt;CMSampleBuffer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeCMSampleBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CVPixelBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;CMSampleBuffer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;sampleBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CMSampleBuffer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CMVideoFormatDescription&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="kt"&gt;CMVideoFormatDescriptionCreateForImageBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;formatDescriptionOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;videoInfo&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;timingInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CMSampleTimingInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CMTimeMake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timescale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;presentationTimeStamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CMTimeMake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timescale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;decodeTimeStamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CMTimeMake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;timescale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;CMSampleBufferCreateForImageBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;allocator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kCFAllocatorDefault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;imageBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dataReady&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;makeDataReadyCallback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;refcon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;formatDescription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;videoInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sampleTiming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;timingInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sampleBufferOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sampleBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;sampleBuffer&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;attachments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CMSampleBufferGetSampleAttachmentsArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sampleBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;createIfNecessary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;NSArray&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attachments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as!&lt;/span&gt; &lt;span class="kt"&gt;NSMutableDictionary&lt;/span&gt;
    &lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kCFBooleanTrue&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;AnyObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kCMSampleAttachmentKey_DisplayImmediately&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;NSString&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sampleBuffer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can finally display the YUV image on screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;videoLayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AVSampleBufferDisplayLayer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;videoLayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;70.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;videoLayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;makeCMSampleBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;imageToYUVCVPixelBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSublayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;videoLayer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5oy0zeb5u1f12fhcdv0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5oy0zeb5u1f12fhcdv0.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A More Efficient Alternative
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;AVSampleBufferDisplayLayer&lt;/code&gt; is a comparatively heavy-duty UI element that conducts extensive hidden state management in the background. The time it takes to initialize a layer of this type is generally unpredictable, and it can sporadically cause animation delays. While &lt;code&gt;AVSampleBufferDisplayLayer&lt;/code&gt; excels at displaying YUV video content, its advanced functionalities aren't necessary for presenting simple images. Instead, we can utilize an obscure feature of UIKit that allows us to assign an IOSurface-backed &lt;code&gt;CVPixelBuffer&lt;/code&gt; directly to &lt;code&gt;CALayer.contents&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;directLayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CALayer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;directLayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;70.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;200.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;directLayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;imageToYUVCVPixelBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSublayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directLayer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run this code in a simulator, you will not see any image. It only works on actual iOS devices and native macOS apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This technique can be trivially expanded to manage YUV+Alpha images (use &lt;code&gt;kCVPixelFormatType_420YpCbCr8BiPlanarFullRange&lt;/code&gt; and adjust the conversion routine as needed).&lt;/p&gt;

&lt;p&gt;A 4:2:0-coded YUV image requires 50% less RAM space than an equivalent RGB image. For illustrative purposes, the code in this article converts an existing RGB image to the YUV format, causing the application to utilize 150% of the RGB-equivalent RAM during the conversion process. To avoid this peak, it is advised to source the image in YUV format directly. This can be achieved using a libjpeg-derived library capable of outputting YUV data directly.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Using Bazel with your iOS Projects</title>
      <dc:creator>Peter Iakovlev</dc:creator>
      <pubDate>Fri, 23 Jun 2023 23:51:11 +0000</pubDate>
      <link>https://dev.to/petertech/using-bazel-with-your-ios-projects-g7e</link>
      <guid>https://dev.to/petertech/using-bazel-with-your-ios-projects-g7e</guid>
      <description>&lt;p&gt;When making a product, there comes a time when you need to work smarter, not harder. This is especially true when your app gets big and complicated. Just like Facebook shared in their &lt;a href="https://engineering.fb.com/2017/11/09/android/rethinking-android-app-compilation-with-buck/" rel="noopener noreferrer"&gt;post&lt;/a&gt; (2017), it's wise to find better ways to manage the building of your app.&lt;/p&gt;

&lt;p&gt;That's where Bazel comes in.&lt;/p&gt;

&lt;p&gt;Bazel is a free tool from Google that helps build your apps. It's great for big, complex apps, but also works well for smaller ones. This article is all about how to use Bazel to make building your iOS apps quicker and easier.&lt;/p&gt;

&lt;p&gt;We'll start with the basics: how to set up Bazel and use it for the first time. By the end of this article, you'll have a good understanding of how to use Bazel and why it can make your app building process better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Take a look at these important companies using Bazel to build their apps, and in some cases, their backend code as well: &lt;a href="https://bazel.build/community/users" rel="noopener noreferrer"&gt;https://bazel.build/community/users&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of these companies have specialized infrastructure teams handling their build tools. Fortunately for us, these teams also contribute to Bazel's open-source code, allowing us to leverage the collective expertise of these professionals to our benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Bazel
&lt;/h2&gt;

&lt;p&gt;To start, visit &lt;a href="https://github.com/bazelbuild/bazel/releases" rel="noopener noreferrer"&gt;https://github.com/bazelbuild/bazel/releases&lt;/a&gt; and select the version you wish to use. If you're new to Bazel, it's recommended to choose the most recent version that isn't labeled as a "pre-release".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frttj3god7m40urwl08vg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frttj3god7m40urwl08vg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Download the file appropriate for your platform. If you're using an Apple Silicon-based computer, your file name will end with &lt;code&gt;-darwin-arm64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, move the downloaded file to a location within your &lt;code&gt;$PATH&lt;/code&gt;, and check to confirm it's operational:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ bazel --version
bazel *.*.*


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For other installation methods (including using Homebrew), visit &lt;a href="https://bazel.build/install/os-x" rel="noopener noreferrer"&gt;https://bazel.build/install/os-x&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;p&gt;We're now ready to create our first Bazel-based iOS app. Although we'll be using Xcode later, we need to manually set up some basic structure first since we're opting for an alternative build system.&lt;/p&gt;

&lt;p&gt;To simplify the process, I've prepared a template: &lt;a href="https://github.com/petertechstories/ios-bazel-template" rel="noopener noreferrer"&gt;https://github.com/petertechstories/ios-bazel-template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the structure of the project folder:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

BUILD
Info.plist
[MyModule]
[Resources]
[Sources]
WORKSPACE


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's unpack what's going on here.&lt;/p&gt;

&lt;p&gt;Let's break down what each of these files and folders do.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;WORKSPACE&lt;/code&gt; is the starting point of our project. It lists all the tools needed to build the app. The contents of this file might seem complex initially, but for the most part, it's a standard setup suitable for Apple platforms. A typical example can be found at &lt;a href="https://github.com/bazelbuild/rules_apple/releases" rel="noopener noreferrer"&gt;https://github.com/bazelbuild/rules_apple/releases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BUILD&lt;/code&gt; provides a detailed description of the project. Think of it like an &lt;code&gt;.xcodeproj&lt;/code&gt; file, but with simpler syntax that's easy for humans to read. For our template project, it's pretty straightforward:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application")
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")

load(
    "@rules_xcodeproj//xcodeproj:defs.bzl",
    "top_level_targets",
    "xcodeproj",
    "xcode_provisioning_profile",
)

load("@build_bazel_rules_apple//apple:apple.bzl", "local_provisioning_profile")

swift_library(
    name = "Sources",
    srcs = [
        "Sources/AppDelegate.swift",
    ],
    data = [
        "//Resources:Main.storyboard",
    ],
    deps = [
        "//MyModule"
    ]
)

local_provisioning_profile(
    name = "xcode_managed_profile",
    profile_name = "iOS Team Provisioning Profile: com.test.BazelApp",
    tags = ["manual"],
)

ios_application(
    name = "App",
    app_icons = ["//Resources:PhoneAppIcon.xcassets"],
    bundle_id = "com.test.BazelApp",
    families = [
        "iphone",
        "ipad",
    ],
    provisioning_profile = ":xcode_managed_profile",
    infoplists = [":Info.plist"],
    launch_storyboard = "//Resources:Launch.storyboard",
    minimum_os_version = "13.0",
    deps = [":Sources"],
    visibility = ["//visibility:public"],
)

xcodeproj(
    name = "xcodeproj",
    project_name = "App",
    tags = ["manual"],
    top_level_targets = top_level_targets(
        labels = [
            ":App",
        ],
        target_environments = ["device", "simulator"],
    ),    
)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It might seem like we're looking at a series of Python function calls, and in a way, we are. Bazel uses a Turing-complete language to understand project descriptions. However, it's important to note that even though it's possible, it's not recommended to perform heavy computations in this space.&lt;/p&gt;

&lt;p&gt;First, we &lt;code&gt;load&lt;/code&gt; a few definitions from our toolset. In Bazel language, the components we import and use to define different parts of the project are called "rules". The &lt;code&gt;load&lt;/code&gt; statements can only reference those rules that have been defined in the &lt;code&gt;WORKSPACE&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Next, we define a Swift module that will hold the app's code. This is done using the &lt;a href="https://github.com/bazelbuild/rules_swift/blob/master/doc/rules.md#swift_library" rel="noopener noreferrer"&gt;swift_library&lt;/a&gt; function. If you're familiar with SwiftPM syntax, this is quite similar, except it's expressed in a style more akin to Python.&lt;/p&gt;

&lt;p&gt;Afterwards, we call the &lt;a href="https://github.com/bazelbuild/rules_apple/blob/master/doc/rules-ios.md#ios_application" rel="noopener noreferrer"&gt;ios_application&lt;/a&gt; function to outline our application. Most of the parameters should be easy to understand. The &lt;code&gt;visibility&lt;/code&gt; parameter is handy in larger projects divided into modules. But for simpler projects like ours, it's perfectly fine to keep it public.&lt;/p&gt;

&lt;p&gt;The remaining part of the &lt;code&gt;BUILD&lt;/code&gt; file is responsible for adding Xcode support to our project, so we're not stuck coding in Notepad.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MyModule&lt;/code&gt; directory houses the definition of a module. It's always a smart move to divide your project into separate, functional units.&lt;/p&gt;

&lt;p&gt;Lastly, &lt;code&gt;Info.plist&lt;/code&gt; and &lt;code&gt;Resources&lt;/code&gt; function just like they would in Xcode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Xcode
&lt;/h2&gt;

&lt;p&gt;Now that the project is set up, we're ready to start working on it. Normally, we would just double-click the &lt;code&gt;.xcodeproj&lt;/code&gt; file, but there isn't one available. Like many other third-party build systems, Bazel doesn't depend on &lt;code&gt;.xcodeproj&lt;/code&gt; files internally. However, it can generate a temporary project file for our use. To create one, run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ bazel run :xcodeproj


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In a few seconds, an &lt;code&gt;App.xcodeproj&lt;/code&gt; file should appear in the project's directory. Double-click it to open Xcode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd51ecd9ankr3833wk2an.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd51ecd9ankr3833wk2an.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This brings us back to our comfort zone. Working with a Bazel-based project in Xcode should feel familiar. The key thing to remember is that when you want to change the project's structure (like adding files or modules), you need to make these changes in the &lt;code&gt;BUILD&lt;/code&gt; files first, then re-generate the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing for Distribution
&lt;/h2&gt;

&lt;p&gt;Let's assume we are ready to send the app for testing or submission to the App Store. How do we get the app archive? Typically, we would use the Archive action in Xcode. With Bazel, we don't even need Xcode for that. Instead, head over to the terminal, and run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ bazel build -c opt :App


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, we're instructing Bazel to build the target &lt;code&gt;:App&lt;/code&gt;, which is our application (as seen in &lt;code&gt;BUILD&lt;/code&gt;), in its Release configuration (where &lt;code&gt;opt&lt;/code&gt; stands for "optimized").&lt;/p&gt;

&lt;p&gt;Once the build completes, you should see the following lines:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Target //:App up-to-date:
  bazel-bin/App.ipa


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This indicates that the build was successful, and the .ipa file can be found at &lt;code&gt;bazel-bin/App.ipa&lt;/code&gt;. This file can then be submitted to the App Store using the Application Loader, or shared with friends or colleagues for AdHoc installation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Bazel is a powerful build system that can greatly simplify the build and distribution process of iOS applications, especially as they become more complex. By integrating Bazel with familiar tools like Xcode, you can harness the best of both worlds - the flexibility and power of Bazel, and the convenience and familiarity of Xcode. While it might take some getting used to, the pay-off in terms of improved efficiency and scalability makes it well worth the effort.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>ios</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Recreating iMessage's Sticker Peel-Off Effect</title>
      <dc:creator>Peter Iakovlev</dc:creator>
      <pubDate>Wed, 21 Jun 2023 14:04:18 +0000</pubDate>
      <link>https://dev.to/petertech/recreating-imessages-sticker-peel-off-effect-i2g</link>
      <guid>https://dev.to/petertech/recreating-imessages-sticker-peel-off-effect-i2g</guid>
      <description>&lt;p&gt;Since the release of iOS 10, iMessage has grown from a simple text messaging app into a lively platform filled with various features. Among these, the introduction of expressive stickers has been a major highlight.&lt;/p&gt;

&lt;p&gt;True to form, Apple extended its celebrated eye for detail into this feature as well. The seemingly straightforward act of applying a sticker onto a message transcends the usual translate-and-scale animations seen on other platforms. Instead, Apple applies the "sticker" metaphor literally, as the chosen item deforms to mimic the physical action of peeling off an actual sticker.&lt;/p&gt;

&lt;p&gt;In this article, we'll reconstruct this particular effect from scratch, leveraging basic trigonometry and some lesser-known features of UIKit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The End Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmlfhavbjzjleu5i2zjgy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmlfhavbjzjleu5i2zjgy.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the end of this article, we will achieve the peel-off effect seen here. Take note of the image's seamless, non-linear bending and the vibrant shadow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bending Images
&lt;/h2&gt;

&lt;p&gt;UIKit does not provide any public APIs that enable us to apply arbitrary non-affine transformations to views. There is a private API, &lt;code&gt;CAMeshTransform&lt;/code&gt; (see this &lt;a href="https://ciechanow.ski/mesh-transforms/" rel="noopener noreferrer"&gt;excellent exploration by Bartosz Ciechanowski&lt;/a&gt;), but using private APIs is strongly discouraged, as it may lead to Apple rejecting your app upon submission to the App Store.&lt;/p&gt;

&lt;p&gt;Given these constraints, we'll opt for the next best alternative, which involves separating an image into several independent layers and manually arranging them to create the illusion of non-uniform transformation.&lt;/p&gt;

&lt;p&gt;From this point forward, assume that all provided code exists within the context of a UIView-derived class (for instance, StickerEffectView). To begin, we'll define the array that will hold the image segments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let containerLayer = CALayer()
var segmentLayers: [CALayer] = []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And define the parameters that will affect the presentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let inset: CGFloat = 20.0
let elevation: CGFloat = 60.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;inset&lt;/code&gt; defines to the size of the "edge" that extends beyond our sticker image, allowing us to add a shadow effect later; &lt;code&gt;elevation&lt;/code&gt; impacts the perceived height at which the sticker will appear to float when we apply the effect.&lt;/p&gt;

&lt;p&gt;Next, we need to arrange the segment layers in a way that assigns each one a portion of the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func rebuildLayers() {
    if bounds.isEmpty {
        return
    }

    let segmentCount = 20
    let boundingSize = CGSize(width: bounds.width + inset * 2.0, height: bounds.height + inset * 2.0)
    let segmentHeight = boundingSize.height / CGFloat(segmentCount)

    for i in 0 ..&amp;lt; segmentCount {
        if segmentLayers.count &amp;lt;= i {
            let segmentLayer = CALayer()

            segmentLayer.anchorPoint = CGPoint()

            let segmentFrame = CGRect(origin: CGPoint(x: 0, y: CGFloat(i) * segmentHeight), size: CGSize(width: boundingSize.width, height: segmentHeight))
            let segmentContentsRect = CGRect(origin: CGPoint(x: segmentFrame.minX / boundingSize.width, y: segmentFrame.minY / boundingSize.height), size: CGSize(width: segmentFrame.width / boundingSize.width, height: segmentFrame.height / boundingSize.height))

            segmentLayer.contentsRect = segmentContentsRect

            containerLayer.addSublayer(segmentLayer)
            segmentLayers.append(segmentLayer)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this function, we divide the image into &lt;code&gt;segmentCount&lt;/code&gt; vertical parts. Each segment is assigned with the exact same image content. To enable them to display only their respective parts, we'll utilize the somewhat obscure &lt;code&gt;contentsRect&lt;/code&gt; property of &lt;code&gt;CALayer&lt;/code&gt;. In essence, this property lets us "crop" the image using the GPU, removing the need for doing that on the CPU.&lt;/p&gt;

&lt;p&gt;Lastly, we will specify the function that updates the segments' positions and rotations to align with our peel-off wave animation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func updateLayers(fraction: CGFloat, reverse: Bool) {
    let segmentContents = contentImage?.cgImage

    for i in 0 ..&amp;lt; segmentLayers.count {
        let segmentLayer = segmentLayers[i]

        segmentLayer.contents = segmentContents

        let topFraction: CGFloat = CGFloat(i) / CGFloat(segmentLayers.count)
        let bottomFraction: CGFloat = CGFloat(i + 1) / CGFloat(segmentLayers.count)

        let topZ = elevation * valueAt(fraction: fraction, t: topFraction, reverse: reverse)
        let bottomZ = elevation * valueAt(fraction: fraction, t: bottomFraction, reverse: reverse)

        let topY = -inset + topFraction * (bounds.height + inset * 2.0)
        let bottomY = -inset + bottomFraction * (bounds.height + inset * 2.0)

        let dy = bottomY - topY
        let dz = bottomZ - topZ
        let angle = -atan2(dy, dz) + .pi * 0.5

        segmentLayer.zPosition = topZ
        segmentLayer.transform = CATransform3DMakeRotation(angle, 1.0, 0.0, 0.0)

        let segmentHeight: CGFloat = sqrt(dy * dy + dz * dz)

        segmentLayer.position = CGPoint(x: -inset, y: topY)

        segmentLayer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width + inset * 2.0, height: segmentHeight))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this function, &lt;code&gt;fraction&lt;/code&gt; represents the animation's progress (ranging from 0.0 to 1.0), and &lt;code&gt;reverse&lt;/code&gt; indicates whether it's a "peel-off" (reverse: false) or a "put-back" (reverse: true) phase. For each layer, the function calculates the locations of its top and bottom edges and positions the layer in a way that accommodates the bending effect.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;updateLayers&lt;/code&gt; function calls the &lt;code&gt;valueAt&lt;/code&gt; function, which we have yet to define. In mathematical terms, &lt;code&gt;valueAt&lt;/code&gt; defines a scalar field (i.e., it returns a floating point number at any given location) that generates the wave effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func valueAt(fraction: CGFloat, t: CGFloat, reverse: Bool) -&amp;gt; CGFloat {
    let windowSize: CGFloat = 0.8

    let effectiveT: CGFloat
    let windowStartOffset: CGFloat
    let windowEndOffset: CGFloat
    if reverse {
        effectiveT = 1.0 - t
        windowStartOffset = 1.0
        windowEndOffset = -windowSize
    } else {
        effectiveT = t
        windowStartOffset = -windowSize
        windowEndOffset = 1.0
    }

    let windowPosition = (1.0 - fraction) * windowStartOffset + fraction * windowEndOffset
    let windowT = max(0.0, min(windowSize, effectiveT - windowPosition)) / windowSize
    let localT = 1.0 - windowFunction(t: windowT)

    return localT
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this function, &lt;code&gt;windowSize&lt;/code&gt; determines the portion of the total image height that will be included in the animatable area. Any area before the window is assigned a value of 1.0 (indicating "peeled-off"), and any area after the window is 0.0 (indicating a stationary position).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;windowFunction&lt;/code&gt; is a separate function that allows us to  experiment with the shape of the wave. For the sake of simplicity, we'll initially set it as a linear function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func windowFunction(t: CGFloat) -&amp;gt; CGFloat {
    return t
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Making it Smoother
&lt;/h2&gt;

&lt;p&gt;If we execute the code we've written so far, we observe the following result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feh9aupg6doej302jm8lw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feh9aupg6doej302jm8lw.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While it looks acceptable, the animation appears a bit harsh and mechanical. We're aiming for a shape that's more along these lines:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnu5b2r95b0pumo20crp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnu5b2r95b0pumo20crp.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thankfully, we can easily accomplish this by updating our window function to utilize a Bézier curve instead of a straight line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func windowFunction(t: CGFloat) -&amp;gt; CGFloat {
    return evaluateBezier(0.5, 0.0, 0.5, 1.0, t)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this straightforward modification, the animation becomes significantly smoother and more appealing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8gvep71n1vnqo7hwstx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8gvep71n1vnqo7hwstx.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the Shadow
&lt;/h2&gt;

&lt;p&gt;The shadow effect can be replicated by adding another set of segments that display a blurred version of the sticker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let shadowContainerLayer: CALayer
var shadowSegmentLayers: [CALayer] = []
...
func rebuildLayers() {
    if bounds.isEmpty {
        return
    }

    let segmentCount = 20
    let boundingSize = CGSize(width: bounds.width + inset * 2.0, height: bounds.height + inset * 2.0)
    let segmentHeight = boundingSize.height / CGFloat(segmentCount)

    for i in 0 ..&amp;lt; segmentCount {
        if segmentLayers.count &amp;lt;= i {
            let segmentLayer = SegmentLayer()
            let shadowSegmentLayer = SegmentLayer()

            segmentLayer.anchorPoint = CGPoint()
            shadowSegmentLayer.anchorPoint = CGPoint()

            let segmentFrame = CGRect(origin: CGPoint(x: 0, y: CGFloat(i) * segmentHeight), size: CGSize(width: boundingSize.width, height: segmentHeight))
            let segmentContentsRect = CGRect(origin: CGPoint(x: segmentFrame.minX / boundingSize.width, y: segmentFrame.minY / boundingSize.height), size: CGSize(width: segmentFrame.width / boundingSize.width, height: segmentFrame.height / boundingSize.height))

            segmentLayer.contentsRect = segmentContentsRect
            shadowSegmentLayer.contentsRect = segmentContentsRect

            containerLayer.addSublayer(segmentLayer)
            shadowContainerLayer.addSublayer(shadowSegmentLayer)

            segmentLayers.append(segmentLayer)
            shadowSegmentLayers.append(shadowSegmentLayer)
        }
    }
}
...
func updateLayers(fraction: CGFloat, reverse: Bool) {
    let segmentContents = contentImage?.cgImage
    let shadowSegmentContents = shadowContentImage?.cgImage

    for i in 0 ..&amp;lt; segmentLayers.count {
        let segmentLayer = segmentLayers[i]
        let shadowSegmentLayer = shadowSegmentLayers[i]

        segmentLayer.contents = segmentContents
        shadowSegmentLayer.contents = shadowSegmentContents

        let topFraction: CGFloat = CGFloat(i) / CGFloat(segmentLayers.count)
        let bottomFraction: CGFloat = CGFloat(i + 1) / CGFloat(segmentLayers.count)

        let topZ = elevation * valueAt(fraction: fraction, t: topFraction, reverse: reverse)
        let bottomZ = elevation * valueAt(fraction: fraction, t: bottomFraction, reverse: reverse)

        let topY = -inset + topFraction * (bounds.height + inset * 2.0)
        let bottomY = -inset + bottomFraction * (bounds.height + inset * 2.0)

        let dy = bottomY - topY
        let dz = bottomZ - topZ
        let angle = -atan2(dy, dz) + .pi * 0.5

        segmentLayer.zPosition = topZ
        segmentLayer.transform = CATransform3DMakeRotation(angle, 1.0, 0.0, 0.0)

        shadowSegmentLayer.zPosition = segmentLayer.zPosition
        shadowSegmentLayer.transform = segmentLayer.transform

        let segmentHeight: CGFloat = sqrt(dy * dy + dz * dz)

        segmentLayer.position = CGPoint(x: -inset, y: topY)
        shadowSegmentLayer.position = segmentLayer.position

        segmentLayer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width + inset * 2.0, height: segmentHeight))
        shadowSegmentLayer.bounds = segmentLayer.bounds
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're now very close to achieving the desired effect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzozpmz9ortvvzom9cutj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzozpmz9ortvvzom9cutj.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the shadow looks good, it would be more effective if it were only rendered beneath the peeled-off portions of the sticker. To fix this, we'll implement a simple gradient mask over the shadow container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let shadowMaskGradient: CAGradientLayer
...
shadowContainerLayer.mask = shadowMaskGradient
...
func updateLayers(fraction: CGFloat, reverse: Bool) {
...
    shadowMaskGradient.colors = (0 ... segmentLayers.count).map { i in
        let t = CGFloat(i) / CGFloat(segmentLayers.count)
        return UIColor(white: 1.0, alpha: valueAt(fraction: fraction, t: t, reverse: reverse)).cgColor
    }
...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can see our peel-off effect in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F871ov2bfw4obu2r87b7e.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F871ov2bfw4obu2r87b7e.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;The iMessage peel-off effect includes a subtle sheen that runs though the sticker during the animation. This can be easily added by incorporating an additional layer of image segments that functions as a mask for the glare. The glare itself can be created with a simple gradient:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2v8u59s6r1ltgmz8n09w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2v8u59s6r1ltgmz8n09w.gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I won't include the code for the glare here, as it primarily reiterates the setup and update functions we've already discussed. The full version can be found at the link below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Visual effects often appear to be magic, until you learn the principles behind them yourself. The iMessage sticker peel-off animation is a visually captivating effect that can add a sense of joy and dynamism in your user interface.&lt;/p&gt;

&lt;p&gt;The complete source code provided at &lt;a href="https://github.com/petertechstories/sticker-effect" rel="noopener noreferrer"&gt;https://github.com/petertechstories/sticker-effect&lt;/a&gt; contains a number of auxiliary functions that were omitted in this article for clarity and brevity.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>programming</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
