<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text">Kyle Howells Blog</title>
  <id>https://ikyle.me/blog/recent.xml</id>
  <updated>2026-01-17T00:51:57.109793+00:00</updated>
  <link href="https://ikyle.me/blog/" />
  <link href="https://ikyle.me/blog/recent.xml" rel="self" />
  <author>
    <name>Kyle Howells</name>
    <uri>https://ikyle.me</uri>
    <email>kyle@ikyle.me</email>
  </author>
  <rights>© 2026 Kyle Howells</rights>
  <generator>Werkzeug</generator>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Loading Oversized 360 Panoramas with Metal</title>
    <id>https://ikyle.me/blog/2026/metal-texture-tiling</id>
    <updated>2026-01-17T00:51:57.109793+00:00</updated>
    <published>2026-01-17T00:51:57.109793+00:00</published>
    <link href="https://ikyle.me/blog/2026/metal-texture-tiling" />
    <summary type="text">Comparing GPU single-texture uploads, vertical strip tiling, grid tiling, and the CPU fallback for huge equirectangular images.</summary>
    <content type="html">&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/metal-texture-tiling/metal-cube-flow.jpeg&#34; alt=&#34;Flowchart showing automatic fallback order single texture → vertical tiles → grid tiles → CPU fallback&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 1. The decision tree our renderer follows whenever a panorama exceeds Metal&#39;s single-texture limit.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt; — Metal caps 2D textures at 16,384 px per side on iOS (32K on recent Macs). To display 20K panoramas you need a fallback ladder: try a single texture upload, then progressively tile the image (vertical strips → grids) before punting to a CPU conversion. This post walks through each rung with simple code, diagrams, and the tradeoffs we measured.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Why the cap exists&lt;/h2&gt;
&lt;p&gt;The texture dimension limit keeps GPUs from having to allocate unbounded surfaces. Anything exceeding the device&#39;s limit must be split into smaller pieces or sampled on the CPU. Here&#39;s an example of how to query the limit based on the Metal GPU family:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;MTLCreateSystemDefaultDevice&lt;/span&gt;()&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Query the actual texture size limit from Metal&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;maxTextureSize&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;MTLDevice&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Int&lt;/span&gt; {
    &lt;span style=&#34;color: #177500&#34;&gt;// Apple10 (M5, A19+) supports 32K textures&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;supportsFamily&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;apple10&lt;/span&gt;) {
        &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;32768&lt;/span&gt;
    }

    &lt;span style=&#34;color: #177500&#34;&gt;// Apple3+ (A9 through M4) support 16K textures&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;supportsFamily&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;apple3&lt;/span&gt;) {
        &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;16384&lt;/span&gt;
    }

    &lt;span style=&#34;color: #177500&#34;&gt;// Older devices (Apple2) have 8K limit&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;8192&lt;/span&gt;
}

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;maxSize&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;maxTextureSize&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;max&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;maxSize&lt;/span&gt; {
    &lt;span style=&#34;color: #177500&#34;&gt;// ok to upload as a single texture&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;On iOS devices (A11+), the limit is 16,384 pixels per dimension. Recent Macs with M5 chips support 32K. Our test panoramas were 11K×5.5K (which fits on all devices) and 19,937×9,969 (which requires tiling on iOS).&lt;/p&gt;
&lt;p&gt;The Metal feature set is listed by Apple here (in a PDF??).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf&#34;&gt;Metal-Shading-Language-Specification.pdf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf&#34;&gt;Metal-Feature-Set-Tables.pdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Metal-Feature-Set-Tables.pdf &amp;gt; &#34;GPU implementation limits by family&#34; &amp;gt; page 7 of 15 &amp;gt; &#34;Resources&#34; &amp;gt; &#34;Maximum 2D texture width and height&#34;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;GPU Family&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Max 2D Texture&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Metal3&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Metal4&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Apple2&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;8,192 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Apple3&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Apple4&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Apple5&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Apple6&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Apple7&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Apple8&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Apple9&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Apple10&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;32,768 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Mac2&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;16,384 px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And these are the devices (or SoCs at least) which correspond to each GPU Family.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/metal-texture-tiling/Metal%20GPUs%20%28Apple%20silicon%29.png&#34; alt=&#34;Metal GPU family texture limits from Apple&amp;#x27;s Metal Feature Set Tables&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Source: &lt;a href=&#34;https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf&#34;&gt;Metal-Feature-Set-Tables.pdf&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Strategy 1 — Single GPU texture&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;+──────────────+    MTLTextureDescriptor.texture2D
|  11K × 5.5K  |───▶ .texture2D, pixelFormat:.bgra8Unorm
+──────────────+    usage: .shaderRead
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Upload via &lt;code&gt;MTKTextureLoader&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;MTKTextureLoader&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;Option&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;] = [
    .&lt;span style=&#34;color: #000&#34;&gt;SRGB&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;,
    .&lt;span style=&#34;color: #000&#34;&gt;textureUsage&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;NSNumber&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;value&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;MTLTextureUsage&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shaderRead&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rawValue&lt;/span&gt;)
]
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;try&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;loader&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;newTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Fastest path. Only limitation: fails if the image exceeds the device&#39;s texture limit. That&#39;s when we escalate.&lt;/p&gt;
&lt;h2&gt;Strategy 2 — Vertical strip tiling&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;Original:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;K&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;×&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;K&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;Split&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;into&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;strips&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;→&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;each&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;K&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;×&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;K&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;┌──────────┬──────────┐&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;          &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;          &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;Tile&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;Tile&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;(UV&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;origin&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;          &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;          &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;└──────────┴──────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Key idea: cut along longitude, keep full latitude. Each tile knows its &lt;code&gt;(uvOrigin, uvSize)&lt;/code&gt; within the source panorama.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tilesX&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;Int&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;ceil&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;Double&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Double&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;maxTextureSize&lt;/span&gt;)))

&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileX&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.&lt;/span&gt;.&amp;lt;&lt;span style=&#34;color: #000&#34;&gt;tilesX&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;start&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;tileX&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;maxTextureSize&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileWidth&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;maxTextureSize&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;start&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;rect&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGRect&lt;/span&gt;(
        &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;start&lt;/span&gt;,
        &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;,
        &lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;tileWidth&lt;/span&gt;,
        &lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;  &lt;span style=&#34;color: #177500&#34;&gt;// Full height for vertical strips&lt;/span&gt;
    )
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cropped&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;cropping&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;to&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;rect&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;try&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;loader&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;newTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;cropped&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;)
  
    &lt;span style=&#34;color: #000&#34;&gt;tiles&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;PanoramaTileTexture&lt;/span&gt;(
        &lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;,
        &lt;span style=&#34;color: #000&#34;&gt;uvOrigin&lt;/span&gt;: [&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;start&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;), &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;],
        &lt;span style=&#34;color: #000&#34;&gt;uvSize&lt;/span&gt;: [&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;tileWidth&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;), &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;]
    ))
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;During the compute pass we bind both textures and choose based on &lt;code&gt;uv.x&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;wrappedU&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;floor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;);
&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;wrappedU&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile1Start&lt;/span&gt;) { &lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile0&lt;/span&gt; } &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile1&lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This cut the 20K panorama from 78s CPU to 0.93s GPU on our benchmarks. Pros: low tile count, simple metadata. Cons: still limited if the image height also exceeds the texture limit.&lt;/p&gt;
&lt;h3&gt;Slicing the image on the CPU&lt;/h3&gt;
&lt;p&gt;The tiling happens in Swift before we touch Metal. We use &lt;code&gt;CGImage.cropping(to:)&lt;/code&gt; to extract each strip, then upload each strip as a separate texture:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Figure out how many tiles we need&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileCount&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;Int&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;ceil&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;Double&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;imageWidth&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Double&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;maxTextureSize&lt;/span&gt;)))

&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.&lt;/span&gt;.&amp;lt;&lt;span style=&#34;color: #000&#34;&gt;tileCount&lt;/span&gt; {
    &lt;span style=&#34;color: #177500&#34;&gt;// Calculate this tile&amp;#39;s horizontal slice&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;startX&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileWidth&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;actualWidth&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;tileWidth&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;imageWidth&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;startX&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;// Crop the strip from the source image&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cropRect&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGRect&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;startX&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;actualWidth&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;imageHeight&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileImage&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;cropping&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;to&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;cropRect&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;

    &lt;span style=&#34;color: #177500&#34;&gt;// Upload to Metal (each tile is now under the size limit)&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;textureLoader&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;newTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;tileImage&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;tiles&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The key is that each tile stores its UV origin so the shader knows where it belongs in the full panorama. Tile 0 starts at U=0, tile 1 starts at U=0.5 (for 2 tiles), etc.&lt;/p&gt;
&lt;h2&gt;Strategy 3 — Grid tiling&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;K&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;×&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;20&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;K&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;hypothetical&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;Split&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;o&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tiles&lt;/span&gt; (&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;×&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;grid&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;┌───────┬───────┐&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;  &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;  &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;├───────┼───────┤&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;  &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;  &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;└───────┴───────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We extend the loader to tile along both axes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tilesX&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;Int&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;ceil&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;Double&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Double&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;maxTextureSize&lt;/span&gt;)))
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tilesY&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;Int&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;ceil&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;Double&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Double&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;maxTextureSize&lt;/span&gt;)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The shader becomes a small loop over metadata:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Tile metadata structure&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;PanoramaTile&lt;/span&gt; {
    &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uvOrigin&lt;/span&gt;;   &lt;span style=&#34;color: #177500&#34;&gt;// Where this tile starts in UV space (e.g., [0.5, 0])&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uvSize&lt;/span&gt;;     &lt;span style=&#34;color: #177500&#34;&gt;// Size of this tile in UV space (e.g., [0.5, 1.0])&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;uint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;textureIndex&lt;/span&gt;; &lt;span style=&#34;color: #177500&#34;&gt;// Which texture slot this tile occupies&lt;/span&gt;
};

&lt;span style=&#34;color: #177500&#34;&gt;// In the compute kernel:&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;wrappedUV&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;floor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;), &lt;span style=&#34;color: #000&#34;&gt;clamp&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;));

&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;uint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tileCount&lt;/span&gt;; &lt;span style=&#34;color: #000&#34;&gt;++i&lt;/span&gt;) {
    &lt;span style=&#34;color: #000&#34;&gt;PanoramaTile&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tiles&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt;];

    &lt;span style=&#34;color: #177500&#34;&gt;// Check if this UV falls within this tile&amp;#39;s region&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;inTileX&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;wrappedUV&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;uvOrigin&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                   &lt;span style=&#34;color: #000&#34;&gt;wrappedUV&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;lt;&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;uvOrigin&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;uvSize&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;);
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;inTileY&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;wrappedUV&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;uvOrigin&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                   &lt;span style=&#34;color: #000&#34;&gt;wrappedUV&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;lt;&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;uvOrigin&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;uvSize&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;);

    &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;inTileX&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;inTileY&lt;/span&gt;) {
        &lt;span style=&#34;color: #177500&#34;&gt;// Remap global UV to local tile UV&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;localUV&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;wrappedUV&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;uvOrigin&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;uvSize&lt;/span&gt;;
        &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileTextures&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;tile&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;textureIndex&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;sampler&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;localUV&lt;/span&gt;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;ASCII of the texture bindings (we cap at 16):&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Metal slot layout
[0] tile0  [1] tile1  [2] tile2  [3] tile3  ...  [15] tile15
[16] output cube texture
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Grid tiling was ~10% faster than vertical strips in our tests (0.84s vs 0.93s for the 20K panorama) because each tile is smaller, which helps texture cache locality near the poles.&lt;/p&gt;
&lt;h3&gt;The tiled compute shader&lt;/h3&gt;
&lt;p&gt;The key challenge in the shader is figuring out &lt;em&gt;which tile&lt;/em&gt; to sample for any given UV coordinate, then remapping that global UV to the tile&#39;s local UV space.&lt;/p&gt;
&lt;p&gt;Here&#39;s the core logic, which runs identically to the single-texture version except for the tile selection:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;kernel&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;equirectangularToCubeTiled&lt;/span&gt;(
    &lt;span style=&#34;color: #000&#34;&gt;texture2d&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile0&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]],
    &lt;span style=&#34;color: #000&#34;&gt;texture2d&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile1&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)]],
    &lt;span style=&#34;color: #177500&#34;&gt;// ... up to 4 tiles&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;texturecube&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;access::write&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeOutput&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;4&lt;/span&gt;)]],
    &lt;span style=&#34;color: #000&#34;&gt;constant&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Uniforms&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;amp;uniforms&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;buffer&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]],
    &lt;span style=&#34;color: #000&#34;&gt;uint3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;thread_position_in_grid&lt;/span&gt;]])
{
    &lt;span style=&#34;color: #177500&#34;&gt;// Same as before: get direction, convert to panorama UV&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeFaceDirection&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;pixelUV&lt;/span&gt;);
    &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;globalUV&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;directionToUV&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt;);

    &lt;span style=&#34;color: #177500&#34;&gt;// NEW: Figure out which tile this UV falls into&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;wrappedU&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;globalUV&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;floor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;globalUV&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;);  &lt;span style=&#34;color: #177500&#34;&gt;// Wrap at seam&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;uint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileIndex&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uint&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;wrappedU&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tileCount&lt;/span&gt;));
    &lt;span style=&#34;color: #000&#34;&gt;tileIndex&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;tileIndex&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tileCount&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;);  &lt;span style=&#34;color: #177500&#34;&gt;// Clamp to valid range&lt;/span&gt;

    &lt;span style=&#34;color: #177500&#34;&gt;// NEW: Remap global U → local tile U&lt;/span&gt;
    &lt;span style=&#34;color: #177500&#34;&gt;// If we have 2 tiles: tile 0 covers U=[0, 0.5), tile 1 covers U=[0.5, 1)&lt;/span&gt;
    &lt;span style=&#34;color: #177500&#34;&gt;// A global U of 0.75 becomes local U of 0.5 in tile 1&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileWidth&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tileCount&lt;/span&gt;);
    &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileStartU&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;tileIndex&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileWidth&lt;/span&gt;;
    &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;localU&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;wrappedU&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileStartU&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tileWidth&lt;/span&gt;;
    &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;localUV&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;localU&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;globalUV&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;);

    &lt;span style=&#34;color: #177500&#34;&gt;// Sample from the correct tile (switch is required in Metal)&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;color&lt;/span&gt;;
    &lt;span style=&#34;color: #A90D91&#34;&gt;switch&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;tileIndex&lt;/span&gt;) {
        &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;color&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile0&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;sampler&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;localUV&lt;/span&gt;); &lt;span style=&#34;color: #A90D91&#34;&gt;break&lt;/span&gt;;
        &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;color&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tile1&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;sampler&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;localUV&lt;/span&gt;); &lt;span style=&#34;color: #A90D91&#34;&gt;break&lt;/span&gt;;
        &lt;span style=&#34;color: #177500&#34;&gt;// ...&lt;/span&gt;
    }

    &lt;span style=&#34;color: #000&#34;&gt;cubeOutput&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;color&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;uint2&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;xy&lt;/span&gt;), &lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The UV remapping math is the heart of tiling. Think of it like this: if you have 2 tiles, tile 0 &#34;owns&#34; the left half (U=0 to 0.5) and tile 1 owns the right half (U=0.5 to 1.0). When the shader wants to sample at global U=0.75, it needs to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Determine that 0.75 falls in tile 1 (the right half)&lt;/li&gt;
&lt;li&gt;Remap 0.75 to local coordinates: &lt;code&gt;(0.75 - 0.5) / 0.5 = 0.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Sample tile 1 at local U=0.5&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/metal-texture-tiling/tile-uv-remap.png&#34; alt=&#34;Diagram showing how global UV coordinates are remapped to local tile coordinates&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 2. The UV remapping process: find which tile contains the sample point, then convert global UV to local tile UV.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Strategy 4 — CPU fallback&lt;/h2&gt;
&lt;p&gt;Sometimes the GPU path still fails: more than 16 tiles needed, out-of-memory errors, or you need bit-exact deterministic output for offline export. The CPU fallback mirrors the GPU shader logic exactly—it&#39;s just ~100× slower because we&#39;re doing millions of texture samples on the CPU instead of GPU cores.&lt;/p&gt;
&lt;h3&gt;Why you need bilinear filtering&lt;/h3&gt;
&lt;p&gt;The GPU&#39;s texture sampler does bilinear filtering automatically. On CPU, we have to implement it ourselves. Without filtering, you&#39;d see blocky pixels when the panorama resolution doesn&#39;t match the cube face resolution exactly.&lt;/p&gt;
&lt;p&gt;Bilinear filtering samples four neighboring pixels and blends them based on where the sample point falls:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;u&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;v&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #000&#34;&gt;SIMD4&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt; {
    &lt;span style=&#34;color: #177500&#34;&gt;// Convert UV to pixel coordinates (may land between pixels)&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fx&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;u&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fy&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;// Get the four corner pixel coordinates&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;x0&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;Int&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;floor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;fx&lt;/span&gt;))
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;x1&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;y0&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;Int&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;floor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;fy&lt;/span&gt;))
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;y1&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;min&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;y0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;// How far between pixels? (0.0 = exactly on x0, 1.0 = exactly on x1)&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tx&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;fx&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x0&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;ty&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;fy&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;y0&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;// Read the four corners and blend&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;c00&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;readPixel&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y0&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;c10&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;readPixel&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y0&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;c01&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;readPixel&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y1&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;c11&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;readPixel&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y1&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;// Interpolate horizontally, then vertically&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;top&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;mix&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;c00&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;c10&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;tx&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;bottom&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;mix&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;c01&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;c11&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;tx&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;mix&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;top&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;bottom&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;ty&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;The conversion loop&lt;/h3&gt;
&lt;p&gt;The CPU conversion uses the same math as the shader. The key to acceptable performance is parallelizing aggressively:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Process all 6 faces in parallel&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;concurrentPerform&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;iterations&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;6&lt;/span&gt;) { &lt;span style=&#34;color: #000&#34;&gt;face&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
    &lt;span style=&#34;color: #177500&#34;&gt;// Within each face, process rows in parallel&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;concurrentPerform&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;iterations&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt;) { &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.&lt;/span&gt;.&amp;lt;&lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt; {
            &lt;span style=&#34;color: #177500&#34;&gt;// Convert pixel → cube face UV → direction → panorama UV&lt;/span&gt;
            &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeUV&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;SIMD2&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(
                (&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;,
                (&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;
            )
            &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;cubeFaceDirection&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;face&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;cubeUV&lt;/span&gt;)
            &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;panoUV&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;directionToUV&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt;)

            &lt;span style=&#34;color: #177500&#34;&gt;// Sample and write (same as GPU, just 100× slower per pixel)&lt;/span&gt;
            &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;color&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;imageSampler&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;u&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;panoUV&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;v&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;panoUV&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;)
            &lt;span style=&#34;color: #000&#34;&gt;writePixel&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;face&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;color&lt;/span&gt;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The nested &lt;code&gt;DispatchQueue.concurrentPerform&lt;/code&gt; gives us parallelism at two levels: faces and rows within each face. On an 8-core Mac, this brings a 20K panorama conversion from ~5 minutes (single-threaded) to ~78 seconds—still 85× slower than the GPU path, but workable as a fallback.&lt;/p&gt;
&lt;p&gt;Pros: works for any size, produces identical results to the GPU path, no Metal texture limits to worry about.&lt;/p&gt;
&lt;p&gt;Cons: 38–78s runtime on modern Macs for 11–20K inputs, ~1.5 GB RAM for the decoded image plus output buffers.&lt;/p&gt;
&lt;h2&gt;Putting it together — automatic ladder&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;switch&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;strategy&lt;/span&gt; {
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; .&lt;span style=&#34;color: #000&#34;&gt;automatic&lt;/span&gt;:
      &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;trySingleTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;) { &lt;span style=&#34;color: #A90D91&#34;&gt;break&lt;/span&gt; }
      &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tryTiling&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;verticalStrips&lt;/span&gt;) { &lt;span style=&#34;color: #A90D91&#34;&gt;break&lt;/span&gt; }
      &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tryTiling&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;grid&lt;/span&gt;) { &lt;span style=&#34;color: #A90D91&#34;&gt;break&lt;/span&gt; }
  
      &lt;span style=&#34;color: #000&#34;&gt;runCPUFallback&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;)
   
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; .&lt;span style=&#34;color: #000&#34;&gt;forceVerticalTiles&lt;/span&gt;:
      &lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;tryTiling&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;verticalStrips&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;runCPUFallback&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;cgImage&lt;/span&gt;)
      &lt;span style=&#34;color: #177500&#34;&gt;// ... etc&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;ASCII flow:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;[Upload single texture?]&lt;/span&gt;
     &lt;span style=&#34;color: #836C28&#34;&gt;| yes&lt;/span&gt;
   &lt;span style=&#34;color: #836C28&#34;&gt;render&lt;/span&gt;
     &lt;span style=&#34;color: #836C28&#34;&gt;| no&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;[Vertical tiles succeed?]&lt;/span&gt;
     &lt;span style=&#34;color: #836C28&#34;&gt;| yes → render&lt;/span&gt;
     &lt;span style=&#34;color: #836C28&#34;&gt;| no&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;[Grid tiles succeed?]&lt;/span&gt;
     &lt;span style=&#34;color: #836C28&#34;&gt;| yes → render&lt;/span&gt;
     &lt;span style=&#34;color: #836C28&#34;&gt;| no&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;[CPU fallback]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Benchmarks&lt;/h2&gt;
&lt;p&gt;All tests performed on Apple Silicon with Release builds. The results are total time from &lt;code&gt;CGImage&lt;/code&gt; input to usable &lt;code&gt;MTLTexture&lt;/code&gt; cube map output:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;Panorama&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;CPU Fallback&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;GPU Vertical Tiles&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;GPU Grid Tiles&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;11K × 5.5K&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;38.2s&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;0.34s&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;0.31s&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;~110×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;19.9K × 10K&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;78.2s&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;0.93s&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;0.84s&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;~85×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/metal-texture-tiling/benchmark-speedup.png&#34; alt=&#34;Bar chart comparing CPU vs GPU conversion times&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 3. Performance comparison showing the dramatic speedup from GPU tiling—up to 110× faster than CPU fallback.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The dramatic speedup comes from three factors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Massive parallelism&lt;/strong&gt; — GPU shader cores process millions of pixels simultaneously&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardware texture sampling&lt;/strong&gt; — Built-in bilinear filtering and optimized memory access patterns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No CPU↔memory bottleneck&lt;/strong&gt; — Data stays on GPU after upload&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Additional observations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vertical tiles&lt;/strong&gt; keep resource usage low (only 2 textures for 20K×10K) and are perfect for most 2:1 panoramas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grid tiles&lt;/strong&gt; cost more texture slots (up to 16) but squeeze out ~10% extra performance and handle non-standard aspect ratios&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU fallback&lt;/strong&gt; remains as the safety net for extremely large assets or offline export scenarios requiring bit-exact determinism&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Memory comparison&lt;/h2&gt;
&lt;p&gt;The GPU tiled path also uses less peak memory than the CPU fallback:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/metal-texture-tiling/memory-comparison.png&#34; alt=&#34;Memory usage comparison between CPU and GPU paths&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 4. Memory breakdown for a 20K panorama. The GPU path can release tiles after upload, reducing peak usage.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Visual aids&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;Panorama&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;            &lt;span style=&#34;color: #000&#34;&gt;Cube&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;face&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;┌───────────────────┐&lt;/span&gt;    &lt;span style=&#34;color: #000&#34;&gt;┌────────┐&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;longitudinal&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;lines│&lt;/span&gt;    &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;  &lt;span style=&#34;color: #A90D91&#34;&gt;sky&lt;/span&gt;   &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;■&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;camera&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;forward&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;    &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;▲&lt;/span&gt;     &lt;span style=&#34;color: #000&#34;&gt;│&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;└───────────────────┘&lt;/span&gt;    &lt;span style=&#34;color: #000&#34;&gt;└────────┘&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/metal-texture-tiling/tiling-comparison.jpeg&#34; alt=&#34;Side-by-side comparison of single texture, vertical tiling, and grid tiling overlays&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 5. Visualising how the same panorama is sliced for each upload strategy before being converted into cube faces.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Left: single texture (fits). Middle: vertical strips (2 tiles). Right: grid tiles (4 tiles). The rendered cube map looks the same—the difference is all under the hood.&lt;/p&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Keep work on the GPU.&lt;/strong&gt; Hardware texture units and compute pipelines devour these conversions in milliseconds once the data is in VRAM.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tile gradually.&lt;/strong&gt; Longitudinal splits cover most panoramas; only fall back to grids when height also exceeds the cap.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leave a CPU escape hatch.&lt;/strong&gt; There will always be some &#34;mega&#34; image, offline export requirement, or simulator configuration that needs it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next time I&#39;ll cover how we stream these tiles incrementally so users see a preview almost instantly, even before the full-resolution cube finishes baking.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Rendering 360° Photos: From Equirectangular Pixels to a View</title>
    <id>https://ikyle.me/blog/2026/render-360-equirectangular</id>
    <updated>2026-01-16T22:55:02.189949+00:00</updated>
    <published>2026-01-16T22:55:02.189949+00:00</published>
    <link href="https://ikyle.me/blog/2026/render-360-equirectangular" />
    <summary type="text">A walkthrough of the math, coordinate systems, and projection code you need to show an equirectangular panorama on screen.</summary>
    <content type="html">&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/render-360-equirectangular/equirectangular-grid.jpeg&#34; alt=&#34;Latitude/longitude grid overlaid on the 11K×5.5K panorama&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 1. Annotated equirectangular source showing meridians, parallels, and the forward camera marker.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/render-360-equirectangular/final-view.jpeg&#34; alt=&#34;Panorama slice with field of view vs the rendered forward cube face&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 2. Left: the source panorama with a 90° frustum overlay at the center. Right: the corresponding region rendered as viewed through the virtual camera.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Treat every pixel in an equirectangular panorama as a latitude/longitude sample on the unit sphere. Convert those angles into a direction vector, rotate it by your virtual camera pose, then sample the texture at the corresponding UV. The math is identical whether you&#39;re rendering in Metal, SceneKit, Unity, or WebGL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. The coordinate system&lt;/h2&gt;
&lt;p&gt;An equirectangular image is essentially a world map projection applied to a sphere. It encodes &lt;code&gt;(longitude, latitude)&lt;/code&gt; over &lt;code&gt;(u, v)&lt;/code&gt; texture space:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Longitude&lt;/strong&gt; spans &lt;code&gt;[-π, +π]&lt;/code&gt; horizontally (left edge = -180°, right edge = +180°)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latitude&lt;/strong&gt; spans &lt;code&gt;[+π/2, -π/2]&lt;/code&gt; vertically (top = north pole, bottom = south pole)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I keep the math straight with this ASCII map:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;      u=0        u=0.5       u=1.0
       ↓           ↓           ↓
+90° ┌─────────────────────────┐ v=0   North pole
     │                         │
  0° │  -180°       0°    +180°│ v=0.5  Equator
     │                         │
-90° └─────────────────────────┘ v=1   South pole
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The key insight: &lt;strong&gt;every pixel in the texture corresponds to a direction in 3D space&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/render-360-equirectangular/coordinate-axes.jpeg&#34; alt=&#34;3D coordinate system showing the sphere with latitude/longitude lines&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 3. The spherical coordinate system. Every direction on this sphere maps to a unique UV coordinate in the panorama texture.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;UV to Direction&lt;/h3&gt;
&lt;p&gt;Given normalized texture coordinates &lt;code&gt;(u, v)&lt;/code&gt; in &lt;code&gt;[0, 1]²&lt;/code&gt;, first convert to angles:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;longitude&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;u&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;pi&lt;/span&gt;  &lt;span style=&#34;color: #177500&#34;&gt;// Range: [-π, +π]&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;latitude&lt;/span&gt; = (&lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;v&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;pi&lt;/span&gt;          &lt;span style=&#34;color: #177500&#34;&gt;// Range: [+π/2, -π/2]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then convert those spherical coordinates to a Cartesian direction vector:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;SIMD3&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(
    &lt;span style=&#34;color: #000&#34;&gt;cos&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;latitude&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cos&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;longitude&lt;/span&gt;),  &lt;span style=&#34;color: #177500&#34;&gt;// x: left/right&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;sin&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;latitude&lt;/span&gt;),                   &lt;span style=&#34;color: #177500&#34;&gt;// y: up/down&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;cos&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;latitude&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sin&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;longitude&lt;/span&gt;)   &lt;span style=&#34;color: #177500&#34;&gt;// z: forward/back&lt;/span&gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This follows the standard convention where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;+X&lt;/strong&gt; points right (longitude = 0°)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;+Y&lt;/strong&gt; points up (latitude = +90°, north pole)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;+Z&lt;/strong&gt; points forward (longitude = +90°)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Direction to UV (the inverse)&lt;/h3&gt;
&lt;p&gt;For rendering, we need the reverse: given a 3D direction, find the texture UV:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;directionToUV&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;SIMD3&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;) -&amp;gt; &lt;span style=&#34;color: #000&#34;&gt;SIMD2&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;longitude&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;atan2&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;)        &lt;span style=&#34;color: #177500&#34;&gt;// Range: [-π, +π]&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;latitude&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;asin&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;clamp&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;))     &lt;span style=&#34;color: #177500&#34;&gt;// Range: [-π/2, +π/2]&lt;/span&gt;

    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;u&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;longitude&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;pi&lt;/span&gt;)) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;           &lt;span style=&#34;color: #177500&#34;&gt;// Range: [0, 1]&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;v&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;latitude&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;pi&lt;/span&gt;)                    &lt;span style=&#34;color: #177500&#34;&gt;// Range: [0, 1]&lt;/span&gt;

    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SIMD2&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color: #000&#34;&gt;u&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;floor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;u&lt;/span&gt;), &lt;span style=&#34;color: #000&#34;&gt;clamp&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;v&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;))  &lt;span style=&#34;color: #177500&#34;&gt;// Wrap u, clamp v&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;u - floor(u)&lt;/code&gt; handles wrapping at the ±180° seam, while &lt;code&gt;v&lt;/code&gt; gets clamped because the poles are singularities.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/render-360-equirectangular/uv-to-direction.png&#34; alt=&#34;Diagram showing UV space mapping to sphere direction&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 4. Left: a sample point in UV texture space. Right: the corresponding direction on the sphere.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;2. Camera rotation &amp;amp; projection&lt;/h2&gt;
&lt;p&gt;The camera&#39;s orientation is represented as a rotation matrix (or quaternion). To find which direction in the panorama corresponds to a given screen pixel, we work backwards: start from the screen, unproject to a view ray, then rotate into world space.&lt;/p&gt;
&lt;h3&gt;Screen to view ray&lt;/h3&gt;
&lt;p&gt;For a pixel at normalized device coordinates &lt;code&gt;(ndcX, ndcY)&lt;/code&gt; where &lt;code&gt;(-1, -1)&lt;/code&gt; is bottom-left and &lt;code&gt;(1, 1)&lt;/code&gt; is top-right:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Convert screen pixel to NDC&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;ndcX&lt;/span&gt; = (&lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pixelX&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewportWidth&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;ndcY&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; (&lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pixelY&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewportHeight&lt;/span&gt;)  &lt;span style=&#34;color: #177500&#34;&gt;// Flip Y&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Create view ray (camera looking down -Z axis)&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;SIMD3&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(
    &lt;span style=&#34;color: #000&#34;&gt;ndcX&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tan&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;fovX&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt;),
    &lt;span style=&#34;color: #000&#34;&gt;ndcY&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tan&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;fovY&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt;),
    &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;
))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Rotate to world space&lt;/h3&gt;
&lt;p&gt;Multiply the view direction by the camera&#39;s rotation matrix to get the world-space direction:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;cameraRotationMatrix&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SIMD4&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;)).&lt;span style=&#34;color: #000&#34;&gt;xyz&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The result is a unit vector pointing from the viewer into the panorama. Pass this to &lt;code&gt;directionToUV()&lt;/code&gt; to sample the texture.&lt;/p&gt;
&lt;h3&gt;The reverse (world to screen)&lt;/h3&gt;
&lt;p&gt;If you need to project a world direction back to screen coordinates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;worldToScreen&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;SIMD3&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;,
                   &lt;span style=&#34;color: #000&#34;&gt;cameraMatrix&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;simd_float4x4&lt;/span&gt;,
                   &lt;span style=&#34;color: #000&#34;&gt;fov&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;SIMD2&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;,
                   &lt;span style=&#34;color: #000&#34;&gt;viewport&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;SIMD2&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;) -&amp;gt; &lt;span style=&#34;color: #000&#34;&gt;SIMD2&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;?
{
    &lt;span style=&#34;color: #177500&#34;&gt;// Transform to view space (inverse rotation)&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;invMatrix&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;cameraMatrix&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;inverse&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;invMatrix&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SIMD4&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;)).&lt;span style=&#34;color: #000&#34;&gt;xyz&lt;/span&gt;

    &lt;span style=&#34;color: #177500&#34;&gt;// Behind the camera? Can&amp;#39;t project.&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt; }

    &lt;span style=&#34;color: #177500&#34;&gt;// Perspective divide&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;ndcX&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;-viewDir&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tan&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;fov&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt;))
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;ndcY&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;-viewDir&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tan&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;fov&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt;))

    &lt;span style=&#34;color: #177500&#34;&gt;// NDC to pixels&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;screenX&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;ndcX&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewport&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;screenY&lt;/span&gt; = (&lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;ndcY&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewport&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;

    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SIMD2&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color: #000&#34;&gt;screenX&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;screenY&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;3. Practical shader&lt;/h2&gt;
&lt;p&gt;Here&#39;s a fragment shader I use inside a Metal &lt;code&gt;MTKView&lt;/code&gt; to render a full equirectangular image without cubemaps:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;fragment&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;equirectangularFragment&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;VertexOut&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;stage_in&lt;/span&gt;]],
                                        &lt;span style=&#34;color: #000&#34;&gt;constant&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CameraUniforms&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;amp;uniforms&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;buffer&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]],
                                        &lt;span style=&#34;color: #000&#34;&gt;texture2d&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pano&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]],
                                        &lt;span style=&#34;color: #000&#34;&gt;sampler&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;panoSampler&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;sampler&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]])
{
    &lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt;(
        &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;ndc&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tanHalfFov&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;,
        &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;ndc&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tanHalfFov&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;,
        &lt;span style=&#34;color: #1C01CE&#34;&gt;-1.0&lt;/span&gt;
    ));
    &lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;((&lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rotationMatrix&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)).&lt;span style=&#34;color: #000&#34;&gt;xyz&lt;/span&gt;);
    &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;lon&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;atan2&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;);
    &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;lat&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;asin&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;clamp&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;-1.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;));
    &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt;(
        (&lt;span style=&#34;color: #000&#34;&gt;lon&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; (&lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;M_PI_F&lt;/span&gt;)) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;,
        &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;lat&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;M_PI_F&lt;/span&gt;)
    );
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pano&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;panoSampler&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;fract&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The CPU just updates &lt;code&gt;uniforms.rotationMatrix&lt;/code&gt; from a quaternion every frame.&lt;/p&gt;
&lt;h2&gt;4. Visualising the math&lt;/h2&gt;
&lt;p&gt;I like to keep scratch graphics around to sanity-check the orientation math:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt; Viewer     Cube Face (forward cam)
    •          ┌──────────────┐
   /|\         │      ↑ y     │
  / | \        │← -x  +z  +x →│
 /  |  \       │      ↓ y     │
/   |   \      └──────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Draw the equator and longitudes directly on the source JPEG with Photoshop or Preview annotations. When the shader runs you should see those lines land where you expect.&lt;/li&gt;
&lt;li&gt;Render a tiny minimap (equirectangular) alongside the 3D view. Whenever you drag the camera, highlight the direction vector on the minimap so it&#39;s obvious where you&#39;re looking.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/render-360-equirectangular/fov-comparison.jpeg&#34; alt=&#34;Field of view comparison showing 40°, 90°, and 140° renders&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 5. The same camera position rendered at different fields of view. Wider FOV shows more of the panorama but with more distortion at the edges.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;5. Sampling only what you see&lt;/h2&gt;
&lt;p&gt;A 20K×10K panorama is ~758 MB once decoded. You rarely need the entire thing at native resolution. Three approaches:&lt;/p&gt;
&lt;h3&gt;Approach 1: Direct equirectangular sampling&lt;/h3&gt;
&lt;p&gt;Load the whole image into a &lt;code&gt;texture2d&amp;lt;float&amp;gt;&lt;/code&gt; and let the fragment shader sample directly using &lt;code&gt;directionToUV()&lt;/code&gt;. This is the simplest approach, but hits Metal&#39;s 16K texture limit on iOS devices (32K on recent Macs).&lt;/p&gt;
&lt;h3&gt;Approach 2: Pre-bake to cube map (recommended)&lt;/h3&gt;
&lt;p&gt;Run a compute shader to convert the equirectangular panorama into six cube faces at load time. Rendering then becomes a simple &lt;code&gt;texturecube&lt;/code&gt; lookup per pixel:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// In the fragment shader - just one line!&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;color&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeTexture&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;sampler&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The cube map conversion uses the same &lt;code&gt;directionToUV()&lt;/code&gt; math, but runs once up front rather than every frame. For each pixel in each cube face, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Calculate the world-space direction for that pixel&lt;/li&gt;
&lt;li&gt;Convert to UV coordinates in the source panorama&lt;/li&gt;
&lt;li&gt;Sample and write to the cube face&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ASCII view of the six cube faces (unfolded):&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;          ┌─────┐
          │ +Y  │  (up)
    ┌─────┼─────┼─────┬─────┐
    │ -X  │ +Z  │ +X  │ -Z  │
    └─────┼─────┼─────┴─────┘
          │ -Y  │  (down)
          └─────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2026/render-360-equirectangular/cube-faces-cross.jpeg&#34; alt=&#34;All six cube faces rendered and arranged in cross layout&#34; /&gt;&lt;br /&gt;
&lt;em&gt;Figure 6. The six cube faces generated from the panorama, arranged in standard cross layout. The +Z (forward) face is highlighted.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This approach saves battery during panning since we&#39;re sampling a cube map (which GPUs are highly optimized for) instead of recomputing spherical coordinates per pixel.&lt;/p&gt;
&lt;h3&gt;Approach 3: Tiled GPU conversion&lt;/h3&gt;
&lt;p&gt;For panoramas exceeding 16K, we can&#39;t upload a single texture. The solution is to split the source image into tiles that each fit within the limit, then sample from multiple textures in the compute shader. See my follow-up post &lt;a href=&#34;https://ikyle.me/blog/metal-texture-tiling&#34;&gt;Taming Oversized Panoramas on Metal&lt;/a&gt; for the full implementation—we measured ~85× speedup over CPU fallback.&lt;/p&gt;
&lt;h2&gt;6. Bringing it together in Swift&lt;/h2&gt;
&lt;p&gt;Now let&#39;s connect all these concepts into a working renderer. The architecture has three main pieces:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A full-screen quad&lt;/strong&gt; — We draw two triangles that cover the entire screen&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A fragment shader&lt;/strong&gt; — For each pixel, it calculates which direction we&#39;re looking and samples the panorama&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A compute shader&lt;/strong&gt; — Converts the equirectangular image to a cube map once at load time&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;6.1 The rendering approach&lt;/h3&gt;
&lt;p&gt;The trick is that we don&#39;t render a 3D sphere. Instead, we render a flat full-screen quad and do all the spherical math in the fragment shader. For each pixel on screen, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Calculate its normalized device coordinates (NDC) from -1 to +1&lt;/li&gt;
&lt;li&gt;Use the FOV to convert that to a view-space ray direction&lt;/li&gt;
&lt;li&gt;Rotate that ray by the camera orientation to get a world direction&lt;/li&gt;
&lt;li&gt;Convert that direction to panorama UV coordinates&lt;/li&gt;
&lt;li&gt;Sample the texture&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is why the vertex shader is trivially simple—it just passes through positions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;vertex&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;VertexOut&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;equirectangularVertex&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;vertexID&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;vertex_id&lt;/span&gt;]],
                                       &lt;span style=&#34;color: #000&#34;&gt;constant&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*positions&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;buffer&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]]) {
    &lt;span style=&#34;color: #000&#34;&gt;VertexOut&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;out&lt;/span&gt;;
    &lt;span style=&#34;color: #000&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;position&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;positions&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;vertexID&lt;/span&gt;], &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;);
    &lt;span style=&#34;color: #000&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;ndc&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;positions&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;vertexID&lt;/span&gt;];  &lt;span style=&#34;color: #177500&#34;&gt;// Pass NDC to fragment shader&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;out&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;6.2 The fragment shader&lt;/h3&gt;
&lt;p&gt;The fragment shader does the heavy lifting. It needs two pieces of data from the CPU: the camera&#39;s rotation matrix and the tangent of half the field of view (pre-computed to avoid trig in the shader).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;CameraUniforms&lt;/span&gt; {
    &lt;span style=&#34;color: #000&#34;&gt;float4x4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;rotationMatrix&lt;/span&gt;;  &lt;span style=&#34;color: #177500&#34;&gt;// Camera orientation as 4x4 matrix&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tanHalfFov&lt;/span&gt;;        &lt;span style=&#34;color: #177500&#34;&gt;// tan(fov/2) for x and y, pre-multiplied by aspect ratio&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;padding&lt;/span&gt;;           &lt;span style=&#34;color: #177500&#34;&gt;// Metal requires 16-byte alignment&lt;/span&gt;
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The shader itself chains together the math from earlier sections:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;fragment&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeFragment&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;VertexOut&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;stage_in&lt;/span&gt;]],
                             &lt;span style=&#34;color: #000&#34;&gt;constant&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CameraUniforms&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;amp;uniforms&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;buffer&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]],
                             &lt;span style=&#34;color: #000&#34;&gt;texturecube&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeTexture&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]],
                             &lt;span style=&#34;color: #000&#34;&gt;sampler&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;textureSampler&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;sampler&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]]) {
    &lt;span style=&#34;color: #177500&#34;&gt;// Step 1: NDC → view ray&lt;/span&gt;
    &lt;span style=&#34;color: #177500&#34;&gt;// The tanHalfFov scales the NDC so the edges of the screen&lt;/span&gt;
    &lt;span style=&#34;color: #177500&#34;&gt;// correspond to the edges of our field of view&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt;(
        &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;ndc&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tanHalfFov&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;,
        &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;ndc&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tanHalfFov&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;,
        &lt;span style=&#34;color: #1C01CE&#34;&gt;-1.0&lt;/span&gt;  &lt;span style=&#34;color: #177500&#34;&gt;// Camera looks down -Z&lt;/span&gt;
    ));

    &lt;span style=&#34;color: #177500&#34;&gt;// Step 2: Rotate view ray → world direction&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;((&lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rotationMatrix&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;viewDir&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)).&lt;span style=&#34;color: #000&#34;&gt;xyz&lt;/span&gt;);

    &lt;span style=&#34;color: #177500&#34;&gt;// Step 3: Sample the cube map&lt;/span&gt;
    &lt;span style=&#34;color: #177500&#34;&gt;// GPU hardware handles the direction → face + UV conversion for us!&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeTexture&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;textureSampler&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;worldDir&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Notice we&#39;re sampling a &lt;code&gt;texturecube&lt;/code&gt; here, not the equirectangular texture directly. Cube maps are what GPUs are optimized for—the hardware handles converting a 3D direction into the correct face and UV coordinates. That&#39;s why we pre-convert the panorama.&lt;/p&gt;
&lt;h3&gt;6.3 The cube map conversion&lt;/h3&gt;
&lt;p&gt;The conversion runs once when loading an image. For each pixel on each of the six cube faces, we need to figure out what direction that pixel represents, then sample the equirectangular source at the corresponding UV.&lt;/p&gt;
&lt;p&gt;The key insight is that &lt;code&gt;gid.z&lt;/code&gt; (the third dimension of our compute grid) represents which cube face we&#39;re on:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;kernel&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;equirectangularToCube&lt;/span&gt;(
    &lt;span style=&#34;color: #000&#34;&gt;texture2d&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;access::sample&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]],
    &lt;span style=&#34;color: #000&#34;&gt;texturecube&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;access::write&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cube&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)]],
    &lt;span style=&#34;color: #000&#34;&gt;constant&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CubeConversionUniforms&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;amp;uniforms&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;buffer&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)]],
    &lt;span style=&#34;color: #000&#34;&gt;uint3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;thread_position_in_grid&lt;/span&gt;]])  &lt;span style=&#34;color: #177500&#34;&gt;// x, y = pixel; z = face index&lt;/span&gt;
{
    &lt;span style=&#34;color: #177500&#34;&gt;// Bounds check (compute shaders can overshoot)&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;6&lt;/span&gt;) {
        &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt;;
    }

    &lt;span style=&#34;color: #177500&#34;&gt;// Convert pixel (0..faceSize) to UV (-1..+1) on the cube face&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; ((&lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;xy&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt;)) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;;

    &lt;span style=&#34;color: #177500&#34;&gt;// Get the 3D direction this pixel represents&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeFaceDirection&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;);

    &lt;span style=&#34;color: #177500&#34;&gt;// Convert direction → panorama UV, then sample&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;panoUV&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;directionToUV&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;direction&lt;/span&gt;);
    &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;color&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sample&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;sampler&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;filter::linear&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;address::repeat&lt;/span&gt;), &lt;span style=&#34;color: #000&#34;&gt;panoUV&lt;/span&gt;);

    &lt;span style=&#34;color: #177500&#34;&gt;// Write to the cube face&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;cube&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;color&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;uint2&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;xy&lt;/span&gt;), &lt;span style=&#34;color: #000&#34;&gt;gid&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;z&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;cubeFaceDirection&lt;/code&gt; function maps each face to its axis. The pattern is: one component is ±1 (the face&#39;s axis), and the other two come from the UV coordinates:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeFaceDirection&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;face&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;float2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;) {
    &lt;span style=&#34;color: #A90D91&#34;&gt;switch&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;face&lt;/span&gt;) {
        &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt;( &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;-uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;-uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;));  &lt;span style=&#34;color: #177500&#34;&gt;// +X face&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;-1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;-uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;,  &lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;));  &lt;span style=&#34;color: #177500&#34;&gt;// -X face&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;,  &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;,  &lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;));   &lt;span style=&#34;color: #177500&#34;&gt;// +Y face (up)&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;3&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;-1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;-uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;));   &lt;span style=&#34;color: #177500&#34;&gt;// -Y face (down)&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;4&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;-uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;,  &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;));   &lt;span style=&#34;color: #177500&#34;&gt;// +Z face (front)&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;-uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;-uv&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;-1&lt;/span&gt;)); &lt;span style=&#34;color: #177500&#34;&gt;// -Z face (back)&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;6.4 Swift: Dispatching the compute shader&lt;/h3&gt;
&lt;p&gt;On the Swift side, we dispatch one thread per output pixel. The grid is &lt;code&gt;(faceSize × faceSize × 6)&lt;/code&gt; to cover all six faces:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Create the output cube texture&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;desc&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;MTLTextureDescriptor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;textureCubeDescriptor&lt;/span&gt;(
    &lt;span style=&#34;color: #000&#34;&gt;pixelFormat&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;bgra8Unorm&lt;/span&gt;,
    &lt;span style=&#34;color: #000&#34;&gt;size&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt;,
    &lt;span style=&#34;color: #000&#34;&gt;mipmapped&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;
)
&lt;span style=&#34;color: #000&#34;&gt;desc&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;usage&lt;/span&gt; = [.&lt;span style=&#34;color: #000&#34;&gt;shaderRead&lt;/span&gt;, .&lt;span style=&#34;color: #000&#34;&gt;shaderWrite&lt;/span&gt;]
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cubeTexture&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;makeTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;descriptor&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;desc&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Dispatch the compute shader&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;commandBuffer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;makeComputeCommandEncoder&lt;/span&gt;()&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setComputePipelineState&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;computePipeline&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;sourceTexture&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;cubeTexture&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setBytes&lt;/span&gt;(&amp;amp;&lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;length&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;MemoryLayout&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #000&#34;&gt;CubeConversionUniforms&lt;/span&gt;&amp;gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;stride&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;// One thread per pixel, across all 6 faces&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dispatchThreads&lt;/span&gt;(
    &lt;span style=&#34;color: #000&#34;&gt;MTLSize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;faceSize&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;depth&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;6&lt;/span&gt;),
    &lt;span style=&#34;color: #000&#34;&gt;threadsPerThreadgroup&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;MTLSize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;16&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;16&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;depth&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)
)
&lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;endEncoding&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;commandBuffer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;commit&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;commandBuffer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;waitUntilCompleted&lt;/span&gt;()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;6.5 Swift: The render loop&lt;/h3&gt;
&lt;p&gt;Each frame, we update the camera orientation and render the full-screen quad:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;render&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;drawable&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;MTLTexture&lt;/span&gt;) {
    &lt;span style=&#34;color: #177500&#34;&gt;// Convert quaternion orientation to matrix for the shader&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CameraUniforms&lt;/span&gt;(
        &lt;span style=&#34;color: #000&#34;&gt;rotationMatrix&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;simd_float4x4&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;orientation&lt;/span&gt;),
        &lt;span style=&#34;color: #000&#34;&gt;tanHalfFov&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;SIMD2&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(
            &lt;span style=&#34;color: #000&#34;&gt;tan&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;fovRadians&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;aspectRatio&lt;/span&gt;,  &lt;span style=&#34;color: #177500&#34;&gt;// X includes aspect ratio&lt;/span&gt;
            &lt;span style=&#34;color: #000&#34;&gt;tan&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;fovRadians&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;)                  &lt;span style=&#34;color: #177500&#34;&gt;// Y is just the FOV&lt;/span&gt;
        )
    )

    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;commandBuffer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;makeRenderCommandEncoder&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;descriptor&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;passDesc&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setRenderPipelineState&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;renderPipeline&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setVertexBuffer&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;quadVertexBuffer&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;offset&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setFragmentBytes&lt;/span&gt;(&amp;amp;&lt;span style=&#34;color: #000&#34;&gt;uniforms&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;length&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;MemoryLayout&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #000&#34;&gt;CameraUniforms&lt;/span&gt;&amp;gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;stride&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setFragmentTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;cubeTexture&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setFragmentSamplerState&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;samplerState&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;drawPrimitives&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;type&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;triangle&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;vertexStart&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;vertexCount&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;6&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;encoder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;endEncoding&lt;/span&gt;()
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;quadVertexBuffer&lt;/code&gt; contains six vertices forming two triangles that cover clip space from (-1,-1) to (1,1).&lt;/p&gt;
&lt;h3&gt;6.6 Putting it in a view&lt;/h3&gt;
&lt;p&gt;Here&#39;s how the pieces connect in a minimal Metal-based panorama viewer:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;final&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;PanoramaView&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;MTKView&lt;/span&gt; {

    &lt;span style=&#34;color: #A90D91&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;renderer&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CubePanoramaRenderer&lt;/span&gt;?

    &lt;span style=&#34;color: #177500&#34;&gt;// Camera state&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;yaw&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;       &lt;span style=&#34;color: #177500&#34;&gt;// Horizontal rotation (radians)&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pitch&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;     &lt;span style=&#34;color: #177500&#34;&gt;// Vertical rotation (radians)&lt;/span&gt;

    &lt;span style=&#34;color: #A90D91&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;init&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGRect&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;MTLDevice&lt;/span&gt;?) {
        &lt;span style=&#34;color: #A90D91&#34;&gt;super&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;init&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt; ?? &lt;span style=&#34;color: #000&#34;&gt;MTLCreateSystemDefaultDevice&lt;/span&gt;())

        &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;renderer&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CubePanoramaRenderer&lt;/span&gt;()
        &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;renderer&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;fieldOfViewDegrees&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;90&lt;/span&gt;

        &lt;span style=&#34;color: #177500&#34;&gt;// Enable continuous redraw for smooth interaction&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isPaused&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;enableSetNeedsDisplay&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;
    }

    &lt;span style=&#34;color: #A90D91&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;draw&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;rect&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGRect&lt;/span&gt;) {
        &lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;drawable&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;currentDrawable&lt;/span&gt;,
              &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;renderer&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;renderer&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; }

        &lt;span style=&#34;color: #177500&#34;&gt;// Build orientation quaternion from yaw/pitch&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;yawQuat&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;simd_quatf&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;angle&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;yaw&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;axis&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;SIMD3&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;))
        &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pitchQuat&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;simd_quatf&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;angle&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;pitch&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;axis&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;SIMD3&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;))
        &lt;span style=&#34;color: #000&#34;&gt;renderer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;orientation&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;yawQuat&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pitchQuat&lt;/span&gt;

        &lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;renderer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;render&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;to&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;drawable&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;clearColor&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;clearColor&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;drawable&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;present&lt;/span&gt;()
    }

    &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;setImage&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;) {
        &lt;span style=&#34;color: #000&#34;&gt;renderer&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;setTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;)
    }

    &lt;span style=&#34;color: #177500&#34;&gt;// Called by gesture recognizer&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pan&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;deltaX&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;deltaY&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;) {
        &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sensitivity&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0.005&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;yaw&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;deltaX&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sensitivity&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;pitch&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;max&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;pi/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #5B269A&#34;&gt;min&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;pi/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;pitch&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;deltaY&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sensitivity&lt;/span&gt;))
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The key integration points:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initialization&lt;/strong&gt; — Create the renderer once; it holds the Metal pipeline state and any converted cube textures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loading images&lt;/strong&gt; — &lt;code&gt;setTexture(from:)&lt;/code&gt; handles the equirectangular → cube conversion automatically (GPU path for images within the device&#39;s texture limit, tiled path for larger).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Orientation&lt;/strong&gt; — Convert user input (pan gestures, device motion) into yaw/pitch angles, then build a quaternion. The renderer multiplies this into the view matrix.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Render loop&lt;/strong&gt; — Each frame, update orientation and call &lt;code&gt;render(to:)&lt;/code&gt;. The fragment shader samples the panorama (or cube map) using the math from sections 1–3.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For snapshots or thumbnails, use &lt;code&gt;makeSnapshot(width:height:)&lt;/code&gt; which renders to an offscreen texture and returns a &lt;code&gt;CGImage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The math here is the same whether you target Metal, SceneKit, Unity, or WebGL. If you can turn &lt;code&gt;(u, v)&lt;/code&gt; into &lt;code&gt;(lon, lat)&lt;/code&gt; and then into a view ray, you can show a convincing 360° photo on any screen.&lt;/p&gt;
&lt;h2&gt;7. Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ikyle.me/blog/metal-texture-tiling&#34;&gt;Taming Oversized Panoramas on Metal&lt;/a&gt; — my follow-up post on GPU tiling for images exceeding the 16K texture limit&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">DateFormatter&#39;s New Year&#39;s Bug - YYYY vs yyyy</title>
    <id>https://ikyle.me/blog/2026/yyyy-vs-YYYY-date-formatter-bug</id>
    <updated>2026-01-11T03:54:19.018058+00:00</updated>
    <published>2026-01-11T03:54:19.018058+00:00</published>
    <link href="https://ikyle.me/blog/2026/yyyy-vs-YYYY-date-formatter-bug" />
    <summary type="text">Why your iOS date formatting breaks around New Year&#39;s and how to fix it</summary>
    <content type="html">&lt;p&gt;Every year around New Year&#39;s Eve, a wave of date-related bugs show up in iOS apps. The reason is a single character mistake in &lt;code&gt;DateFormatter&lt;/code&gt;: using &lt;code&gt;YYYY&lt;/code&gt; instead of &lt;code&gt;yyyy&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;The Bug&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;DateFormatter&lt;/code&gt; has two different year specifiers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;yyyy&lt;/code&gt;&lt;/strong&gt; - Calendar year (what you almost always want)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;YYYY&lt;/code&gt;&lt;/strong&gt; - ISO week-based year (rarely what you want)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The ISO week-based year can differ from the calendar year during the first and last few days of the year. When a week spans two calendar years, the ISO week-based year uses the year that contains the majority of that week.&lt;/p&gt;
&lt;h2&gt;When This Bites You&lt;/h2&gt;
&lt;p&gt;On December 30th, 2024 (a Monday), the ISO week-based year is already &lt;strong&gt;2025&lt;/strong&gt; because that Monday starts ISO week 1 of 2025. But the calendar year is still 2024. This year it showed up Monday 29th December 2025, the first day of the week it becomes 2026.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;Foundation&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;DateFormatter&lt;/span&gt;()
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dec30_2024&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;Calendar&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;current&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;date&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;DateComponents&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;year&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;2024&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;month&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;12&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;day&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;30&lt;/span&gt;))&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Wrong - uses ISO week-based year&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dateFormat&lt;/span&gt; = &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;YYYY-MM-dd&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;string&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;dec30_2024&lt;/span&gt;))
&lt;span style=&#34;color: #177500&#34;&gt;// Output: 2025-12-30&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Correct - uses calendar year&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dateFormat&lt;/span&gt; = &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;yyyy-MM-dd&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;string&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;dec30_2024&lt;/span&gt;))
&lt;span style=&#34;color: #177500&#34;&gt;// Output: 2024-12-30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here&#39;s a table showing how dates around the 2019/2020 boundary behave differently:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;Date&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Weekday&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;yyyy (Calendar)&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;YYYY (ISO Week)&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;ISO Week&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;2019-12-29&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Sunday&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;2019-12-29&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;2019-12-29&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;2019-12-30&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Monday&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;2019-12-30&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;2020-12-30&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;2019-12-31&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Tuesday&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;2019-12-31&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;2020-12-31&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;2020-01-01&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Wednesday&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;2020-01-01&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;2020-01-01&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Notice how December 30th and 31st, 2019 show as &#34;2020&#34; when using &lt;code&gt;YYYY&lt;/code&gt; because they fall in ISO week 1 of 2020.&lt;/p&gt;
&lt;h2&gt;Real-World Bug Example&lt;/h2&gt;
&lt;p&gt;Imagine you&#39;re generating log filenames in your app:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;DateFormatter&lt;/span&gt;()
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dec31_2019&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;Calendar&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;current&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;date&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;DateComponents&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;year&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;2019&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;month&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;12&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;day&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;31&lt;/span&gt;))&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Bug: This creates a file with the wrong year!&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dateFormat&lt;/span&gt; = &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;#39;&amp;#39;YYYY-MM-dd&amp;#39;.txt&amp;#39;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;string&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;dec31_2019&lt;/span&gt;))
&lt;span style=&#34;color: #177500&#34;&gt;// Output: 2020-12-31.txt&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Correct: Uses the actual calendar year&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dateFormat&lt;/span&gt; = &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;#39;&amp;#39;yyyy-MM-dd&amp;#39;.txt&amp;#39;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;formatter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;string&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;dec31_2019&lt;/span&gt;))
&lt;span style=&#34;color: #177500&#34;&gt;// Output: 2019-12-31.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Your 31st December 2019 log file ends up named &lt;code&gt;2020-12-31.txt&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;yyyy&lt;/code&gt;&lt;/strong&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User-facing dates&lt;/li&gt;
&lt;li&gt;Filenames and paths&lt;/li&gt;
&lt;li&gt;Database records&lt;/li&gt;
&lt;li&gt;API date strings&lt;/li&gt;
&lt;li&gt;Anything where you want the actual calendar year&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;YYYY&lt;/code&gt;&lt;/strong&gt; only when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Displaying ISO 8601 week dates (e.g., &#34;2020-W01-3&#34;)&lt;/li&gt;
&lt;li&gt;Specifically working with ISO week numbering&lt;/li&gt;
&lt;li&gt;Always paired with the week number format specifier (&lt;code&gt;ww&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Rule&lt;/h2&gt;
&lt;p&gt;If you&#39;re not explicitly working with ISO week numbers, you want lowercase &lt;code&gt;yyyy&lt;/code&gt;. The uppercase &lt;code&gt;YYYY&lt;/code&gt; variant exists for a specific use case (week-based date systems), and using it for general date formatting is almost always a bug.&lt;/p&gt;
&lt;p&gt;This is one of those bugs that&#39;s invisible 360+ days of the year, then strikes during New Year&#39;s when everyone&#39;s on vacation.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Porting an HTML5 Parser to Swift</title>
    <id>https://ikyle.me/blog/2025/swift-justhtml-porting-html5-parser-to-swift</id>
    <updated>2025-12-18T05:00:59.926593+00:00</updated>
    <published>2025-12-18T05:00:59.926593+00:00</published>
    <link href="https://ikyle.me/blog/2025/swift-justhtml-porting-html5-parser-to-swift" />
    <summary type="text">How I built swift-justhtml with Claude - from 0% to 100% html spec test compliance, finding crash bugs with fuzzing, and the performance optimization needed to match javascript&#39;s speed in Swift</summary>
    <content type="html">&lt;h1&gt;Porting an HTML5 Parser to Swift&lt;/h1&gt;
&lt;p&gt;Emil Stenström spent months building &lt;a href=&#34;https://github.com/EmilStenstrom/justhtml&#34;&gt;justhtml&lt;/a&gt;, a Python HTML5 parser that achieves 100% compliance with the &lt;a href=&#34;https://github.com/html5lib/html5lib-tests&#34;&gt;html5lib test suite&lt;/a&gt;. He &lt;a href=&#34;https://friendlybit.com/python/writing-justhtml-with-coding-agents/&#34;&gt;wrote about the process&lt;/a&gt; - it involved starting from scratch multiple times, pivoting strategies when things weren&#39;t working, and iterating with AI coding agents until every edge case was handled.&lt;/p&gt;
&lt;p&gt;Simon Willison then &lt;a href=&#34;https://simonwillison.net/2025/Dec/15/porting-justhtml/&#34;&gt;ported it to JavaScript&lt;/a&gt; in 4.5 hours. He had GPT-5.2 inspect the Python codebase, create a specification, and then just told it &#34;do the rest, commit and push often&#34; while he decorated for Christmas.&lt;/p&gt;
&lt;p&gt;Looking around after reading those posts, I saw on the Github page:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;JustHTML	✅ 100%	✅ Yes	⚡ Fast
BeautifulSoup	🔴 4%	✅ Yes	🐢 Slow
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I have some projects which use BeautifulSoup and know how slow it can be. So I switched some of my python projects from &lt;code&gt;BeautifulSoup&lt;/code&gt; to Emil Stenström&#39;s new &lt;a href=&#34;https://github.com/EmilStenstrom/justhtml&#34;&gt;justhtml&lt;/a&gt; library and loved it.&lt;/p&gt;
&lt;p&gt;After that I started wishing I had a similar &#34;known good&#34; stable pure swift HTML parsing library for use in my Swift projects (the main language I actually write and use day to day).&lt;br /&gt;
Then I realised, I couple probably just have Claude make one. The same way Emil Stenström made the library originally. The tests!&lt;/p&gt;
&lt;p&gt;So I built &lt;a href=&#34;https://github.com/kylehowells/swift-justhtml&#34;&gt;swift-justhtml&lt;/a&gt;, a port of the &lt;code&gt;justhtml&lt;/code&gt; API to Swift which also passes 100% of the &lt;code&gt;html5lib-tests&lt;/code&gt; like the original python version.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;The Agentic Feedback Loop&lt;/h1&gt;
&lt;p&gt;LLMs are amazing at writing mostly correct code. But only mostly. Coding Agents like Claude Code and Codex are so powerful because they allow for instant feedback and iteration. The models can try to compile or run the code they just wrote, see the error and fix it. Repeatedly if needed.&lt;br /&gt;
So any programming task you can give them which fits that loop, fast iteration, quick feedback on any errors soon after they are made, guidance if the error has actually been fixed or not, and tools they can run to inspect, debug and test in different ways are perfect.&lt;/p&gt;
&lt;p&gt;This project, with a fully spec&#39;d out set of tests via the &lt;a href=&#34;https://github.com/html5lib/html5lib-tests&#34;&gt;html5lib test suite&lt;/a&gt;, is perfect.&lt;br /&gt;
Both the input and output are text, something LLMs understand.&lt;br /&gt;
The task is to transform from one to another, via a CLI tool the coding agent can run itself to check its work.&lt;br /&gt;
And detailed feedback, including on regressions or things it has broken by accident while making another change, via the fully spec&#39;d test suite.&lt;/p&gt;
&lt;p&gt;So I downloaded the tests, onto my home dev server, created an empty swift library/package directory structure and setup the CLI tools needed. Then also downloaded the python and js implementations alongside the tests and told Claude Code with Opus 4.5 (which I still find produces the most reliable code, even if Codex GPT 5 is smarter at debugging errors) to look at the public API interface the python and js versions offer, and then to create a rough spec for a similar Swift API. This was to try to allow the model room to adjust the API to Swift&#39;s type system and idioms by not being too strict it be the same public facing API.&lt;/p&gt;
&lt;p&gt;I then told it to setup the swift library tests to load and run the html5lib tests, and report how many pass/fail, and to implement the HTML parser, run the tests. To then work on fixing the failing tests, and re-run the tests. And to repeat that until it had 100% of the tests passing.&lt;/p&gt;
&lt;p&gt;It took two days of intensive work with Claude working away in the background iterating on the tests, sometimes trying to cheat (we&#39;ll get back to that), and then after a lot more work on performance tuning than I thought would be needed! Here&#39;s how it actually went.&lt;/p&gt;
&lt;h2&gt;Racing to 100%&lt;/h2&gt;
&lt;p&gt;The initial commit was a straightforward smoke test of a valid HTML string to test and verify the DOM tree works &lt;code&gt;&amp;lt;p&amp;gt;Hello&amp;lt;/p&amp;gt;&lt;/code&gt;. This was because I remember&#39;ed Emil&#39;s blog post mentioned he started with this before having it dive into edge cases and handling invalid input and that sounded very wise, so I made sure to start from a known point too.&lt;/p&gt;
&lt;p&gt;Then came the core implementation. HTML5 parsing is deceptively complex. The specification defines 67 distinct tokenizer states, 23 tree builder insertion modes, and algorithms with names like &#34;adoption agency&#34; and &#34;Noah&#39;s Ark clause.&#34;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://html.spec.whatwg.org/multipage/parsing.html#the-list-of-active-formatting-elements&#34;&gt;This is the Noah&#39;s Ark clause. But with three per family instead of two.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first real implementation got to 53% test compliance - just over half the html5lib tests passing.&lt;/p&gt;
&lt;p&gt;From there, it was a steady but slow climb. Each commit tackled another piece of the spec:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Foreign content handling (SVG and MathML have their own rules)&lt;/li&gt;
&lt;li&gt;Foster parenting (table elements need special handling)&lt;/li&gt;
&lt;li&gt;The full adoption agency algorithm (for misnested formatting elements)&lt;/li&gt;
&lt;li&gt;Template element handling&lt;/li&gt;
&lt;li&gt;Entity parsing with partial matches&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After a few hours of this, we were at 97.3% compliance. Then progress slowed dramatically.&lt;/p&gt;
&lt;p&gt;At 99.6% (1763/1770 tests), the model hit a wall. The remaining 7 tests were genuinely hard edge cases - complex template/table/SVG interactions, multiply-nested table foster parenting, and a new HTML feature called &lt;code&gt;&amp;lt;selectedcontent&amp;gt;&lt;/code&gt; that I&#39;d never heard of before. The model choose instead to documented them as &#34;remaining edge cases&#34; and moved on to adding features like CSS selectors and streaming parsing, just skipping implementing these entirely.&lt;/p&gt;
&lt;p&gt;When I noticed, it was actually quite difficult to make the agent go back and include them, it really wanted to just mark them as skipped and consider that a pass.&lt;/p&gt;
&lt;p&gt;Eventually I stopped the agent, had a second fresh agent clear away and delete all the skipping code, and then restarted the first agent telling it to resume working through the failing tests. Which now included the ones it tried to skip.&lt;/p&gt;
&lt;p&gt;The fixes turned out to be surprisingly small. The select mode marker handling needed a single check. The nested table problem needed explicit handling in two insertion modes. The final commit that achieved 100% compliance added just 34 lines to the tree builder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Select mode marker handling (webkit02.dat test 49):&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;// Insert marker when entering select mode to prevent reconstruction&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;// of formatting elements from outside select&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; .&lt;span style=&#34;color: #000&#34;&gt;select&lt;/span&gt;:
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;activeFormattingElements&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;marker&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Eventually: &lt;strong&gt;1,770/1,770 tree construction tests passing&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Performance Reality Check&lt;/h2&gt;
&lt;p&gt;Having achieved 100% test coverage I thought I was now done (minus setting up some documentation and API examples).&lt;/p&gt;
&lt;p&gt;I ran the benchmarks comparing Swift, JavaScript, and Python implementations parsing the same HTML files as I had all 3 downloaded to my computer already:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;Implementation&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Parse Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Swift&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;308ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Python&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;417ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;108ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Swift was only 1.4x faster than Python. JavaScript was &lt;strong&gt;2.9x faster than Swift&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This was not the result I expected from a compiled language. I knew python was generally slow, but also expected js to be slow. So expected the Javascript might be around the same or a bit faster.&lt;/p&gt;
&lt;p&gt;I wasn&#39;t expecting the js version to by 4x faster than the python version, and for the Swift version to be barely faster than the Python.&lt;/p&gt;
&lt;p&gt;If you remember one of the lines from Swift&#39;s introduction (which has been shown to be not true in practice over an over again since then) was &#34;python like code, with speed faster than C&#34;.&lt;/p&gt;
&lt;p&gt;However, I then remembered, Swift&#39;s string are famously &#34;spec correct&#34; and slow. And HTML parsing is mostly string processing.&lt;/p&gt;
&lt;h3&gt;The Fuzzer Finds a Bug&lt;/h3&gt;
&lt;p&gt;Before investigating performance, I wanted to check my library was reliable and stable, before worrying about making it fast. So I also ran the test files for the crash scenarios Emil&#39;s fuzzer had found crashes in his implementation against my implementation.&lt;/p&gt;
&lt;p&gt;Fortunately, none of those crashed my library, but I also might just have been lucky. So I had Claude Code write and run ran a swift fuzzer of our own - generating millions of random and malformed HTML documents to test parser robustness.&lt;/p&gt;
&lt;p&gt;After running those hundred&#39;s of thousands of fuzzer tests, the fuzzer found 1 crash. I had told the coding agent not to fix any fuzzer crashes it found, but to investigate and create a test file which reproducible the crash reliably.&lt;br /&gt;
Once we had that test file setup I had it fix the issue and resume fuzzing the library.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Input: &lt;span style=&#34;color: #000&#34;&gt;&amp;lt;table&amp;gt;&amp;lt;/table&amp;gt;&amp;lt;li&amp;gt;&amp;lt;table&amp;gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
Context: select fragment
Result: SIGSEGV (segmentation fault)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The parser was hitting infinite recursion. When parsing table-related tags inside a select fragment context, &lt;code&gt;popUntil(&#34;select&#34;)&lt;/code&gt; had no effect (select wasn&#39;t on the stack, just the context), and &lt;code&gt;resetInsertionMode()&lt;/code&gt; would restore select mode, causing an infinite loop when reprocessing the tag.&lt;/p&gt;
&lt;p&gt;Interestingly, simpler variants didn&#39;t crash:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;table&amp;gt;&amp;lt;/table&amp;gt;&lt;/code&gt; in select - fine&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;table&amp;gt;&amp;lt;/table&amp;gt;&amp;lt;li&amp;gt;&lt;/code&gt; in select - fine&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;li&amp;gt;&amp;lt;table&amp;gt;&amp;lt;/table&amp;gt;&lt;/code&gt; in select - fine&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;table&amp;gt;&amp;lt;/table&amp;gt;&amp;lt;li&amp;gt;&amp;lt;table&amp;gt;&amp;lt;/table&amp;gt;&lt;/code&gt; in select - &lt;strong&gt;crash&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was the specific sequence that triggered infinite recursion. The fix was checking if select was context-only and, if so, clearing the context and switching directly to inBody mode.&lt;/p&gt;
&lt;p&gt;This is why fuzzing is useful, especially for testing input handling - the html5lib tests don&#39;t cover every possible fragment parsing scenario.&lt;/p&gt;
&lt;h2&gt;The Performance Hunt&lt;/h2&gt;
&lt;p&gt;With the crash fixed, all the html5lib test passing, none of the other test files in the other repos causing crashes or failures, and my own fuzzer failing to find any issues, I turned back to performance. The 2.9x gap with JavaScript was troubling me.&lt;/p&gt;
&lt;p&gt;I now had a reliable, stable swift implementation of the html5 spec with a &lt;code&gt;justhtml&lt;/code&gt; style API to parse and process it. So I didn&#39;t want to break that, or make the code an unrecognisable mess in pursuit of performance.&lt;/p&gt;
&lt;p&gt;So I created a &lt;code&gt;turbo&lt;/code&gt; branch and started profiling. The initial breakdown showed roughly 50/50 time split between tokenizer and tree builder. Both needed work to speed this up.&lt;/p&gt;
&lt;h3&gt;Swift Strings to raw UTF-8 bytes&lt;/h3&gt;
&lt;p&gt;Unfortunately Swift strings are slow, and if you want to go faster one of the most straightforward decisions to avoid them.&lt;/p&gt;
&lt;p&gt;So I gave the agent some benchmarking tools, sample code and guidance on how to research what was causing the time and told the agent to carefully profile, experiment, benchmark and then re-run the tests and fix any regressions caused. If it found a faster solution which still passed all the test to commit it and then start again, profiling to find the now slowest part and experiment with ways to optimise that.&lt;/p&gt;
&lt;p&gt;The first optimization was switching from Swift&#39;s &lt;code&gt;String.Index&lt;/code&gt; to raw UTF-8 bytes. Swift&#39;s string handling is Unicode-correct but expensive - &lt;code&gt;String.Index&lt;/code&gt; advancement is O(n) because it has to account for grapheme clusters.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Before: String.Index iteration&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;ch&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;html&lt;/span&gt; { ... }  &lt;span style=&#34;color: #177500&#34;&gt;// O(n) per access&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// After: Byte-level access&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;bytes&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;ContiguousArray&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #A90D91&#34;&gt;UInt8&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color: #000&#34;&gt;html&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;utf8&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.&lt;/span&gt;.&amp;lt;&lt;span style=&#34;color: #000&#34;&gt;bytes&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;count&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;byte&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;bytes&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt;]  &lt;span style=&#34;color: #177500&#34;&gt;// O(1) access&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Converting to &lt;code&gt;ContiguousArray&amp;lt;UInt8&amp;gt;&lt;/code&gt; gave immediate gains:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;302ms → 261ms&lt;/strong&gt; (14% faster)&lt;/p&gt;
&lt;h3&gt;Batch text insertion&lt;/h3&gt;
&lt;p&gt;Next was batch text insertion. The tree builder was creating a new text node for every character token from the tokenizer. So instead now it builds up a buffer of raw bytes coming in, until it gets to the character it needs to switch on, then at that point it converts the buffer its been building up into a string object. Instead of repeatedly turning each char into a string and appending the string with the pending buffer string each time. So we only pay the object creation and conversion cost once.&lt;/p&gt;
&lt;p&gt;Coalescing consecutive characters before insertion:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;261ms → 182ms&lt;/strong&gt; (30% faster)&lt;/p&gt;
&lt;h3&gt;Avoiding Memory Allocation&lt;/h3&gt;
&lt;p&gt;Then I directed it to look into memory allocation and ways to reduce how many objects we are creating in the loops, the agent found inline array literals in hot paths:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// This creates a temporary array on EVERY tag&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; [&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;td&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;th&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;tr&amp;quot;&lt;/span&gt;].&lt;span style=&#34;color: #5B269A&#34;&gt;contains&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt;) { ... }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Moving these to module-level &lt;code&gt;Set&amp;lt;String&amp;gt;&lt;/code&gt; constants eliminated thousands of allocations per document:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;182ms → 172ms&lt;/strong&gt; (6% faster)&lt;/p&gt;
&lt;h3&gt;Batch scanning for tag names&lt;/h3&gt;
&lt;p&gt;The biggest single win came from batch scanning for tag names. Instead of building names character-by-character with string concatenation, scan ahead to find the delimiter and extract the whole chunk at once, again avoiding paying the string creation and modification cost more than once:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;159ms → 118ms&lt;/strong&gt; (26% faster)&lt;/p&gt;
&lt;h3&gt;Removing the other inline array literals&lt;/h3&gt;
&lt;p&gt;The final optimizations involved hunting down every remaining inline array literal in the tree builder. 7 more were found in &lt;code&gt;processStartTagInBody&lt;/code&gt; that were creating arrays on every single tag. For a &#34;span&#34; tag (common), the code was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create and search a 10-element array (head tags) - no match&lt;/li&gt;
&lt;li&gt;Create and search a 26-element array (block tags) - no match&lt;/li&gt;
&lt;li&gt;Create and search an 11-element array (table tags) - no match&lt;/li&gt;
&lt;li&gt;Keep searching...&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Converting these to static Sets:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;118ms → 98ms&lt;/strong&gt; (17% faster)&lt;/p&gt;
&lt;h3&gt;Final Result&lt;/h3&gt;
&lt;p&gt;One more pass with buffer reuse instead reassigning the object to a new empty &lt;code&gt;[:]&lt;/code&gt; to clear it brought the final improvement to:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;302ms → 97ms&lt;/strong&gt; - a 3.1x speedup.&lt;/p&gt;
&lt;p&gt;The Swift implementation now matched or just barely beat the JavaScript. But it took ~20 optimization commits, detailed profiling, and completely throwing out Swift&#39;s string APIs in favor of raw byte manipulation. The JavaScript version does none of this - it just uses simple, readable character-by-character processing with &lt;code&gt;array.push()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;More on that in a follow-up post about V8&#39;s performance.&lt;/p&gt;
&lt;h3&gt;Merging and Polish&lt;/h3&gt;
&lt;p&gt;With the turbo branch proving out, I merged it back to main. The rest of work was setting up documentation and examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DocC documentation with GitHub Pages deployment&lt;/li&gt;
&lt;li&gt;Example CLI tools (htmltool, html2md, extractlinks, fetchpage)&lt;/li&gt;
&lt;li&gt;A Swift Playground for interactive experimentation&lt;/li&gt;
&lt;li&gt;Memory usage benchmarking of the 3 languages implementations.&lt;/li&gt;
&lt;li&gt;README improvements&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Final Result&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;8,953 tests passing:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tree construction: 1,831&lt;/li&gt;
&lt;li&gt;Tokenizer: 6,810&lt;/li&gt;
&lt;li&gt;Serializer: 230&lt;/li&gt;
&lt;li&gt;Encoding: 82&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; 97ms to parse 2.5MB of HTML across 5 Wikipedia articles. 4x faster than Python, matching JavaScript.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zero dependencies.&lt;/strong&gt; Pure Swift using only Foundation (and even that lightly now String isn&#39;t being used much).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fuzz tested.&lt;/strong&gt; Millions of malformed documents without crashes.&lt;/p&gt;
&lt;h2&gt;How Do Other Swift Libraries Compare?&lt;/h2&gt;
&lt;p&gt;After finishing, I was curious how existing Swift HTML libraries fared against the same test suite. So downloaded and benchmarked the ones which supported linux. The results were:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;Library&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Pass Rate&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;&lt;strong&gt;swift-justhtml&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;100% (1831/1831)&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;Kanna&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;94.4% (1542/1633)&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Uses libxml2 (HTML 4.01 parser)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;SwiftSoup&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;87.9% (1436/1633)&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Infinite loop on 197 tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;LilHTML&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;47.4% (775/1634)&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;Crashes on 52% of tests&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Kanna and LilHTML both use libxml2 under the hood. libxml2 is a C library that implements HTML 4.01 parsing, not the WHATWG HTML5 specification that browsers actually now use. It&#39;s fast (native C code) but won&#39;t handle modern HTML correctly.&lt;/p&gt;
&lt;p&gt;SwiftSoup is a port of Java&#39;s Jsoup library. It hit infinite loops on all 197 tests in tests16.dat - edge cases involving script tags.&lt;/p&gt;
&lt;p&gt;LilHTML was the most surprising. It wraps libxml2 but crashes on over half the test inputs due to unhandled NULL returns. Not sure why the big difference to Kanna (it&#39;s possible I&#39;m doing something wrong with the test for 1 of them. I didn&#39;t look into this that closely).&lt;/p&gt;
&lt;h2&gt;What I Learned&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HTML5 parsing is harder than it looks.&lt;/strong&gt; It looks like simple XML parsing, but that went out the window when XHTML was abandoned, now the specification is thousands of pages. And the edge cases interact in surprising ways.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test suites are invaluable.&lt;/strong&gt; The html5lib tests gave concrete pass/fail feedback for every change. Without them, LLM coding agents would not be able to tackle a task of this size!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Given tools and feedback LLM agents are now incredible powerful!&lt;/strong&gt;: The key is packaging your task in a format which lets the llm check how it is going, and get feedback on what it did right or wrong so it can iterate it&#39;s way to a solution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fuzzing finds real bugs.&lt;/strong&gt; The select fragment crash would never have been caught by the standard test suite alone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Swift&#39;s performance isn&#39;t automatic.&lt;/strong&gt; Being a compiled language doesn&#39;t guarantee speed unfortunately. Understanding memory allocation, string handling, and avoiding unnecessary work matters a lot if you want fast Swift code. Otherwise Swift can quite easily end up as slow to run as it is to compile.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;V8 is WAY faster than I thought!&lt;/strong&gt; Even the super optimised version of &lt;code&gt;swift-justhtml&lt;/code&gt; with minimal String use, raw byte processing, no inline arrays or anything else which could cause allocations still only draws level to the default straightforward js implementation running via V8 in node.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI coding agents are transformative for this kind of work.&lt;/strong&gt; To reiterate: The tight feedback loop - run tests, see failures, propose fix, repeat - is exactly what they&#39;re good at. I spent my time on architecture decisions and code review rather than typing out implementations.&lt;/p&gt;
&lt;p&gt;Which is basically &lt;a href=&#34;https://friendlybit.com/python/writing-justhtml-with-coding-agents/#practical-tips-for-working-with-coding-agents&#34;&gt;the same conclusion Emil reached&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/swift-justhtml&#34;&gt;Github: swift-justhtml on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kylehowells.github.io/swift-justhtml/documentation/justhtml/&#34;&gt;DocC: swift-justhtml API Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/EmilStenstrom/justhtml&#34;&gt;Github: Original justhtml (Python)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/simonw/justjshtml&#34;&gt;Github: justjshtml (JavaScript)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://friendlybit.com/python/writing-justhtml-with-coding-agents/&#34;&gt;Emil&#39;s blog post on building justhtml&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://simonwillison.net/2025/Dec/15/porting-justhtml/&#34;&gt;Simon&#39;s blog post on porting to JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">How to Build Text Based Image Search</title>
    <id>https://ikyle.me/blog/2025/how-to-build-text-to-image-search</id>
    <updated>2025-10-27T06:21:40.012243+00:00</updated>
    <published>2025-10-27T06:21:40.012243+00:00</published>
    <link href="https://ikyle.me/blog/2025/how-to-build-text-to-image-search" />
    <summary type="text">Using multi-modal text and image embedding models for text based and image based image search with python and transformers.</summary>
    <content type="html">&lt;p&gt;Searching for images is an image classification task. There are 2 types of image classification:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://huggingface.co/tasks/image-classification&#34;&gt;Normal Image classification&lt;/a&gt;.&lt;br /&gt;
Classify an image into a preset list of categories.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://huggingface.co/tasks/zero-shot-image-classification&#34;&gt;Zero-shot Image classification&lt;/a&gt;.&lt;br /&gt;
Classify an image into classes previously unseen during training of a model.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Normal Image classification&lt;/h2&gt;
&lt;p&gt;These models are trained with a list of categories and predicts which of these categories the image falls into.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/how-to-build-text-to-image-search/lake-smaller.jpg&#34; alt=&#34;Lake&#34; /&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;transformers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pipeline&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;# Create the pipeline with the `google/vit-base-patch16-224` model&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;clf&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pipeline&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;image-classification&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;google/vit-base-patch16-224&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;# Get the categories&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;clf&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;temp/lake-smaller.jpg&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;lakeside, lakeshore&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.9137738943099976&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;seashore, coast, seacoast, sea-coast&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.017617039382457733&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;promontory, headland, head, foreland&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.016670119017362595&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;valley, vale&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.007515666540712118&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;dam, dike, dyke&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.004389212466776371&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can then save these categories (at least the ones scoring above &lt;code&gt;0.1&lt;/code&gt;) in a database and then use these to search and for images matching the search term.&lt;/p&gt;
&lt;p&gt;However, the above approach will match &#34;lake&#34; but not &#34;water&#34;, or &#34;river&#34;.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://huggingface.co/google/vit-base-patch16-224&#34;&gt;&lt;code&gt;google/vit-base-patch16-224&lt;/code&gt;&lt;/a&gt; is a small &lt;code&gt;86.6M params&lt;/code&gt; (&lt;code&gt;346 MB&lt;/code&gt;) size model which predicts one of the 1000 ImageNet classes.&lt;/p&gt;
&lt;p&gt;There is also the larger &lt;a href=&#34;https://huggingface.co/google/vit-large-patch16-224&#34;&gt;&lt;code&gt;google/vit-large-patch16-224&lt;/code&gt;&lt;/a&gt; model (1.22 GB) which is more accurate, but larger and slower.&lt;/p&gt;
&lt;p&gt;The name means:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;vit&lt;/strong&gt;: Vision Transformer (ViT).&lt;br /&gt;
&lt;strong&gt;base&lt;/strong&gt;: the size of the model.&lt;br /&gt;
&lt;strong&gt;patch16&lt;/strong&gt;: Images are presented to the model as a sequence of fixed-size patches (resolution 16x16), which are linearly embedded.&lt;br /&gt;
&lt;strong&gt;224&lt;/strong&gt;: All images are resized to a resolution of 224x224 pixels before being fed to the model.&lt;/p&gt;
&lt;h1&gt;Zero-shot Image classification&lt;/h1&gt;
&lt;p&gt;In order to get arbitrary matching and score the similarity between the search term and the image, regardless of what the search term is you need a zero-shot image classification model.&lt;/p&gt;
&lt;p&gt;These are embedding models which have a shared embedding space where the embeddings of text representations of an image, and the image data itself are trained to end up with similar coordinates in the embedding space.&lt;/p&gt;
&lt;p&gt;This means when you calculate the distance between the search terms embedding, and the embeddings of several images you get how similar each one is to the search term.&lt;/p&gt;
&lt;p&gt;This also means &#34;ice&#34; will be similar to photos of &#34;snow&#34; even though the words are different.&lt;/p&gt;
&lt;p&gt;2 good models for this are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://openai.com/index/clip/&#34;&gt;OpenAI CLIP&lt;/a&gt; model.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/papers/2502.14786&#34;&gt;Google SigLIP 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;CLIP&lt;/strong&gt; large is a &lt;code&gt;0.4B&lt;/code&gt; (1.71GB) model. The smaller &lt;code&gt;openai/clip-vit-base-patch32&lt;/code&gt; is only &lt;code&gt;605MB&lt;/code&gt; and is the more popular version. Both as fairly small, quick models.&lt;br /&gt;
However, the models were released 2021, and since then newer models have been trained which surpass them in benchmarks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SigLIP 2&lt;/strong&gt; is a family of models released by Google in February 2025, which claims to outperform their previous counterparts at all model scales in core capabilities.&lt;br /&gt;
The models come in &lt;a href=&#34;https://huggingface.co/collections/google/siglip2&#34;&gt;various sizes&lt;/a&gt;: &lt;a href=&#34;https://huggingface.co/google/siglip2-base-patch16-512&#34;&gt;ViT-B (86M)&lt;/a&gt;, &lt;a href=&#34;https://huggingface.co/google/siglip2-large-patch16-256&#34;&gt;L (303M)&lt;/a&gt;, &lt;a href=&#34;https://huggingface.co/google/siglip2-so400m-patch16-naflex&#34;&gt;So400m (400M)&lt;/a&gt;, and &lt;a href=&#34;https://huggingface.co/google/siglip2-giant-opt-patch16-384&#34;&gt;g (1B)&lt;/a&gt;.&lt;br /&gt;
There are also &lt;a href=&#34;https://huggingface.co/google/siglip2-so400m-patch14-224&#34;&gt;fixed size versions&lt;/a&gt; which resize and crop images to a fixed square resolution, or &lt;a href=&#34;https://huggingface.co/google/siglip2-base-patch16-naflex&#34;&gt;flexible resolution and aspect ratio versions&lt;/a&gt;, which divide the image into the fixed size tiles, but divide up images of &lt;a href=&#34;https://huggingface.co/google/siglip2-base-patch16-naflex&#34;&gt;different sizes and resolutions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FG-CLIP 2&lt;/strong&gt; is a new model released in October which claims to further improve image classification results.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/how-to-build-text-to-image-search/fg-clip-2-benchmarks.jpg&#34; alt=&#34;FG-CLIP 2 Benchmarks&#34; /&gt;&lt;/p&gt;
&lt;p&gt;It comes in &lt;a href=&#34;https://huggingface.co/qihoo360/fg-clip2-base&#34;&gt;base (1.54GB)&lt;/a&gt;, &lt;a href=&#34;https://huggingface.co/qihoo360/fg-clip2-large&#34;&gt;large (3.59GB)&lt;/a&gt;, and &lt;a href=&#34;https://huggingface.co/qihoo360/fg-clip2-so400m&#34;&gt;so400m (4.62GB)&lt;/a&gt; versions.&lt;/p&gt;
&lt;h2&gt;Simple Zero-shot Classification Example&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/how-to-build-text-to-image-search/lake-smaller.jpg&#34; alt=&#34;Lake&#34; /&gt;&lt;/p&gt;
&lt;p&gt;If you just want to classify an image into several categories which you provide the huggingface transformers pipeline.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;transformers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pipeline&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;classifier&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pipeline&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;zero-shot-image-classification&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;model=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;openai/clip-vit-large-patch14-336&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;candidate_labels&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;2 cats&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a plane&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a remote&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a bear&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a ocean&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a water&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a river&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a lake&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a pond&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;rain&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a mountain&amp;quot;&lt;/span&gt;,
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a car&amp;quot;&lt;/span&gt;,
]
&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;load_image&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;temp/lake-smaller.jpg&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image_classifier&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;candidate_labels&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.9330312609672546&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a lake&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.02649594098329544&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a water&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.023041551932692528&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a river&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.01358820404857397&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a pond&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0017832380253821611&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a ocean&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0006893535610288382&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a mountain&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0004389677778817713&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a phone&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.00038122545811347663&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a remote&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0002814349136315286&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a plane&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.00019272200006525964&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;rain&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;3.4059521567542106e-05&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a car&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;3.0467408578260802e-05&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a bear&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.143596091424115e-05&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;2 cats&amp;#39;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Using the &lt;code&gt;google/siglip2-so400m-patch16-naflex&lt;/code&gt; model we get roughly the same results, though the scores are all much lower (the order is still good though).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;transformers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pipeline&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;image_classifier&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pipeline&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;model=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;google/siglip2-so400m-patch16-naflex&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;task=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;zero-shot-image-classification&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;outputs&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image_classifier&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;candidate_labels&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;outputs&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.1465340554714203&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a lake&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0038345942739397287&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a water&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.001178617007099092&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a river&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.00041009532287716866&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a ocean&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.00018598142196424305&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a mountain&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;8.42182053020224e-05&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a pond&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;3.1798244890524074e-05&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;rain&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0240605661238078e-05&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a plane&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.7888405636767857e-06&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;2 cats&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.6506612610101001e-06&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a bear&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;5.040944870415842e-07&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a phone&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;4.662872470362345e-07&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a car&amp;#39;&lt;/span&gt;}
{&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;score&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;3.0268274997524713e-08&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;a remote&amp;#39;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Text to Image Search&lt;/h2&gt;
&lt;p&gt;However, if we want to build up a database of image embeddings and do image text and image based searches against those embeddings we need to get the embeddings from these models.&lt;/p&gt;
&lt;p&gt;Example Images&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/how-to-build-text-to-image-search/lake-smaller.jpg&#34; alt=&#34;Lake&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/how-to-build-text-to-image-search/cats.jpg&#34; alt=&#34;cats&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/how-to-build-text-to-image-search/bmw-i7.jpg&#34; alt=&#34;bmw-i7&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/how-to-build-text-to-image-search/beech-tree.jpg&#34; alt=&#34;beech-tree&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Generate Image Embeddings&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;torch&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;PIL&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Image&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;transformers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CLIPProcessor&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;CLIPModel&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;numpy&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;np&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;torch.nn.functional&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;F&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;generate_image_embedding&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image_path&lt;/span&gt;):
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    Generate an L2-normalized image embedding.&lt;/span&gt;

&lt;span style=&#34;color: #C41A16&#34;&gt;    Args:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        image_path: Path to the image file&lt;/span&gt;

&lt;span style=&#34;color: #C41A16&#34;&gt;    Returns:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Tuple of (filename, embedding vector as numpy array)&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;get_device&lt;/span&gt;()
    &lt;span style=&#34;color: #000&#34;&gt;model&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;processor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;get_clip_model&lt;/span&gt;()

    &lt;span style=&#34;color: #177500&#34;&gt;# Load and convert image to RGB&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Image.open&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image_path&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;.convert&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;RGB&amp;quot;&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;# Generate embedding&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;torch.no_grad&lt;/span&gt;():
        &lt;span style=&#34;color: #000&#34;&gt;inputs&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;processor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;images=image&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;return_tensors=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;pt&amp;quot;&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;.to&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;features&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;model.get_image_features&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;**inputs&lt;/span&gt;)
        &lt;span style=&#34;color: #177500&#34;&gt;# L2 normalize for proper cosine similarity&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;features&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;F.normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;features&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;p=&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;dim=-&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;features.cpu&lt;/span&gt;()&lt;span style=&#34;color: #000&#34;&gt;.numpy&lt;/span&gt;()[&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;]

    &lt;span style=&#34;color: #177500&#34;&gt;# Extract just the filename from the path&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image_path.split&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;/&amp;quot;&lt;/span&gt;)[&lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;]

    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Generate Text Images.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;generate_text_embedding&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;query_text&lt;/span&gt;):
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    Generate an L2-normalized text embedding.&lt;/span&gt;

&lt;span style=&#34;color: #C41A16&#34;&gt;    Args:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        query_text: Text query string&lt;/span&gt;

&lt;span style=&#34;color: #C41A16&#34;&gt;    Returns:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Embedding vector as numpy array&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;get_device&lt;/span&gt;()
    &lt;span style=&#34;color: #000&#34;&gt;model&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;processor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;get_clip_model&lt;/span&gt;()

    &lt;span style=&#34;color: #A90D91&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;torch.no_grad&lt;/span&gt;():
        &lt;span style=&#34;color: #000&#34;&gt;inputs&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;processor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text=&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;query_text&lt;/span&gt;], &lt;span style=&#34;color: #000&#34;&gt;return_tensors=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;pt&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;padding=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;.to&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;device&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;features&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;model.get_text_features&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;**inputs&lt;/span&gt;)
        &lt;span style=&#34;color: #177500&#34;&gt;# L2 normalize&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;features&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;F.normalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;features&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;p=&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;dim=-&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;features.cpu&lt;/span&gt;()&lt;span style=&#34;color: #000&#34;&gt;.numpy&lt;/span&gt;()[&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;]

    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Load the image embedding&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;load_images&lt;/span&gt;():
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    Load all images and generate their embeddings.&lt;/span&gt;

&lt;span style=&#34;color: #C41A16&#34;&gt;    Returns:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        List of tuples: [(filename, embedding), ...]&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Loading and embedding images...&amp;quot;&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;embeddings&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; []

    &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image_path&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;IMAGE_PATHS&lt;/span&gt;:
        &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;  Processing {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;image_path.split&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt;)[&lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;]&lt;span style=&#34;color: #C41A16&#34;&gt;}...&amp;quot;&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;generate_image_embedding&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image_path&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;embeddings.append&lt;/span&gt;((&lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt;))

    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;Generated embeddings for {&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;len&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;embeddings&lt;/span&gt;)&lt;span style=&#34;color: #C41A16&#34;&gt;} images\n&amp;quot;&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;embeddings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Compare image embedding.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;search&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;query_text&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;image_embeddings&lt;/span&gt;):
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    Search for images matching the text query.&lt;/span&gt;

&lt;span style=&#34;color: #C41A16&#34;&gt;    Args:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        query_text: Text description to search for&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        image_embeddings: List of (filename, embedding) tuples&lt;/span&gt;

&lt;span style=&#34;color: #C41A16&#34;&gt;    Returns:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        List of (filename, similarity_score) tuples sorted by similarity (best first)&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;Searching for: &amp;#39;{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;query_text&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;#39;&amp;quot;&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;# Generate text embedding&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;text_embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;generate_text_embedding&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;query_text&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;# Compare with all image embeddings&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; []
    &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;image_embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image_embeddings&lt;/span&gt;:
        &lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cosine_similarity&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text_embedding&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;image_embedding&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;results.append&lt;/span&gt;((&lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt;))

    &lt;span style=&#34;color: #177500&#34;&gt;# Sort by similarity (highest first)&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;results.sort&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;key=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;lambda&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;[&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;], &lt;span style=&#34;color: #000&#34;&gt;reverse=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)

    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;\nResults:&amp;quot;&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;-&amp;quot;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;:
        &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;:30s} | Similarity: {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;:.4f}&amp;quot;&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;-&amp;quot;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;()

    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Example compare the images.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;image_embeddings&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;load_images&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;search&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a beautiful lake with mountains&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;image_embeddings&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Results&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Searching for: &amp;#39;a beautiful lake with mountains&amp;#39;

Results:
----------------------------------------------------
lake-smaller.jpg               | Similarity: 0.2865
beech-tree.jpg                 | Similarity: 0.1330
cats.jpg                       | Similarity: 0.1164
bmw-i7.jpg                     | Similarity: 0.1073
----------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Searching for: &amp;#39;luxury car&amp;#39;

Results:
----------------------------------------------------
bmw-i7.jpg                     | Similarity: 0.2513
beech-tree.jpg                 | Similarity: 0.1920
cats.jpg                       | Similarity: 0.1818
lake-smaller.jpg               | Similarity: 0.1685
----------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Searching for: &amp;#39;tree in nature&amp;#39;

Results:
----------------------------------------------------
beech-tree.jpg                 | Similarity: 0.2992
lake-smaller.jpg               | Similarity: 0.2080
cats.jpg                       | Similarity: 0.1543
bmw-i7.jpg                     | Similarity: 0.1377
----------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Searching for: &amp;#39;water and nature&amp;#39;

Results:
----------------------------------------------------
lake-smaller.jpg               | Similarity: 0.2672
beech-tree.jpg                 | Similarity: 0.2209
cats.jpg                       | Similarity: 0.1669
bmw-i7.jpg                     | Similarity: 0.1512
----------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Search By Image&lt;/h2&gt;
&lt;p&gt;You can also do an image search by generating an image embedding and using that for the comparisons instead of using the text embedding.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;search&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image_path&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;image_embeddings&lt;/span&gt;):
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;Searching for: &amp;#39;{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;image_path&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;#39;&amp;quot;&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;# Generate text embedding&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;_&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;search_image_embed&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;generate_image_embedding&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image_path&lt;/span&gt;)

    &lt;span style=&#34;color: #177500&#34;&gt;# Compare with all image embeddings&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; []
    &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;image_embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image_embeddings&lt;/span&gt;:
        &lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cosine_similarity&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;search_image_embed&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;image_embedding&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;results.append&lt;/span&gt;((&lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt;))

    &lt;span style=&#34;color: #177500&#34;&gt;# Sort by similarity (highest first)&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;results.sort&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;key=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;lambda&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;[&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;], &lt;span style=&#34;color: #000&#34;&gt;reverse=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)

    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Results:&amp;quot;&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;-&amp;quot;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;:
        &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;:30s} | Similarity: {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;:.4f}&amp;quot;&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;-&amp;quot;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;()

    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As the image embeddings and text embeddings are designed to end up in a similar location in the multi-dimensional embedding space either can be used for lookups and get good results.&lt;/p&gt;
&lt;h1&gt;References&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gist.github.com/kylehowells/d7daed03d7d3b42ffa4bb9868168c439&#34;&gt;Gist: Blog Post Image Search Scripts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ikyle.me/blog/2025/binary-quantized-embeddings&#34;&gt;How to use sqlite-vec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/asg017/sqlite-vec&#34;&gt;Github: asg017/sqlite-vec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://alexgarcia.xyz/sqlite-vec/&#34;&gt;sqlite-vec&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Image Classification References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/tasks/image-classification&#34;&gt;huggingface docs: Image Classification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/google/vit-base-patch16-224&#34;&gt;huggingface: google/vit-base-patch16-224&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/google/vit-large-patch16-224&#34;&gt;huggingface: google/vit-large-patch16-224&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Zero-Shot Image Classification References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/tasks/zero-shot-image-classification&#34;&gt;huggingface docs: Zero-Shot Image Classification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;CLIP References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://openai.com/index/clip/&#34;&gt;OpenAI: CLIP: Connecting text and images&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/openai/CLIP&#34;&gt;Github: openai/CLIP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/docs/transformers/model_doc/clip&#34;&gt;huggingface docs: clip &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/openai/clip-vit-base-patch32&#34;&gt;huggingface: openai/clip-vit-base-patch32&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/openai/clip-vit-large-patch14&#34;&gt;huggingface: openai/clip-vit-large-patch14&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SigLIP 2 References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/papers/2502.14786&#34;&gt;huggingface papers: SigLIP 2: Multilingual Vision-Language Encoders with Improved Semantic Understanding, Localization, and Dense Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2502.14786&#34;&gt;arxiv: SigLIP 2: Multilingual Vision-Language Encoders with Improved Semantic Understanding, Localization, and Dense Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/google/siglip2-base-patch16-naflex&#34;&gt;huggingface: google/siglip2-base-patch16-naflex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/google/siglip2-so400m-patch14-224&#34;&gt;huggingface: google/siglip2-so400m-patch14-224&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/google-research/big_vision/blob/main/big_vision/configs/proj/image_text/README_siglip2.md&#34;&gt;Github: google/big_vision siglip2.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;FG-CLIP 2 References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://360cvgroup.github.io/FG-CLIP/&#34;&gt;FG-CLIP 2 Project Page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/papers/2510.10921&#34;&gt;huggingface papers: FG-CLIP 2: A Bilingual Fine-grained Vision-Language Alignment Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2510.10921&#34;&gt;arxiv: FG-CLIP 2: A Bilingual Fine-grained Vision-Language Alignment Model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/collections/qihoo360/fg-clip-2&#34;&gt;huggingface fg-clip-2 collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/qihoo360/fg-clip2-base&#34;&gt;huggingface qihoo360/fg-clip2-base&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/qihoo360/fg-clip2-large&#34;&gt;huggingface: qihoo360/fg-clip2-large&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/qihoo360/fg-clip2-so400m&#34;&gt;huggingface: qihoo360/fg-clip2-so400m&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/360CVGroup/FG-CLIP&#34;&gt;GitHub: 360CVGroup/FG-CLIP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Talk: Claude Code &amp; The Evolution of Agentic Coding</title>
    <id>https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding</id>
    <updated>2025-07-16T13:38:15.478599+00:00</updated>
    <published>2025-07-16T13:38:15.478599+00:00</published>
    <link href="https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding" />
    <summary type="text">Boris Cherny&#39;s talk on how Claude Code represents a new paradigm in AI-powered development tools, exploring the evolution from punch cards to natural language programming</summary>
    <content type="html">&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Lue8K2jqfKk&#34;&gt;Boris Cherny&#39;s talk on how Claude Code represents a new paradigm in AI-powered development tools&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Boris Cherny, creator of Claude Code at Anthropic, presents a compelling vision of how programming is evolving in the age of AI. He traces the evolution from punch cards to natural language programming, explaining why Claude Code takes an intentionally simple, unopinionated approach to AI-powered development.&lt;/p&gt;
&lt;p&gt;The talk covers Claude Code&#39;s core features—terminal integration, IDE support, GitHub integration, and SDK—while sharing practical tips for effective use, including code Q&amp;amp;A, teaching Claude your tools, and leveraging Plan Mode for complex tasks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/01-introduction-thesis.jpg&#34; alt=&#34;Introduction/Main thesis&#34; title=&#34;Claude Code Introduction&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Lue8K2jqfKk&#34;&gt;YouTube Video (17m)&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;Boris Cherny, creator of Claude Code at Anthropic, presents a compelling vision of how programming is evolving in the age of AI. In this talk from the AI Startup School in San Francisco, he explores the tension between exponentially improving AI models and the products we build to harness them. Claude Code&#39;s intentionally simple, unopinionated approach represents a fundamental shift in how developers interact with AI—providing direct access to powerful models without unnecessary abstraction layers.&lt;/p&gt;
&lt;h1&gt;Talk Chapters&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=0&#34;&gt;0:00&lt;/a&gt; The model is moving really fast, on exponential. The product is struggling to keep up.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=85&#34;&gt;1:25&lt;/a&gt; Programming evolution: from punch cards to natural language&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=345&#34;&gt;5:45&lt;/a&gt; Ed, the first text editor - still available on Unix systems 50 years later&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=502&#34;&gt;8:22&lt;/a&gt; GitHub Copilot and Devin: the leap to AI-powered coding&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=630&#34;&gt;10:30&lt;/a&gt; Claude Code&#39;s philosophy: intentionally simple and general&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=735&#34;&gt;12:15&lt;/a&gt; Terminal, IDE, GitHub integration, and SDK features&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=840&#34;&gt;14:00&lt;/a&gt; Tips for effective use: code Q&amp;amp;A, teaching tools, and workflows&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=930&#34;&gt;15:30&lt;/a&gt; New Plan Mode feature and the power of context&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=1000&#34;&gt;16:40&lt;/a&gt; Multi-Claude workflows and the future&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Talk Summary&lt;/h1&gt;
&lt;h2&gt;The Exponential Growth Challenge&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/01-introduction-thesis.jpg&#34; alt=&#34;Introduction/Main thesis&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&#34;The model is moving really fast. It&#39;s on exponential. It&#39;s getting better at coding very, very quickly,&#34; Boris begins. &#34;And the product is kind of struggling to keep up. We&#39;re trying to figure out what product to build that&#39;s good enough for a model like this.&#34;&lt;/p&gt;
&lt;p&gt;This tension between rapidly advancing AI capabilities and the products we build to harness them is at the heart of Claude Code&#39;s philosophy. Rather than trying to predict the perfect interface for AI-assisted coding, Claude Code takes a deliberately minimal approach—providing the &#34;bare minimum&#34; to let developers experience and experiment with the model directly.&lt;/p&gt;
&lt;h2&gt;Getting Started with Claude Code&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/02-installation-instructions.jpg&#34; alt=&#34;Installation instructions&#34; /&gt;&lt;/p&gt;
&lt;p&gt;For those new to Claude Code, getting started is straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Head to claude.ai/code to install it&lt;/li&gt;
&lt;li&gt;Run the installation from NPM&lt;/li&gt;
&lt;li&gt;As of the talk, Claude Code supports both Claude Pro and Claude Max plans&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&#34;Just try it out. Tell us what you think,&#34; Boris encourages, emphasizing the experimental nature of the tool.&lt;/p&gt;
&lt;h2&gt;The Evolution of Programming Languages&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/03-evolution-programming-languages.jpg&#34; alt=&#34;Evolution of programming languages&#34; /&gt;&lt;/p&gt;
&lt;p&gt;To understand where we&#39;re heading, Boris takes us on a journey through programming history. &#34;Programming is changing, and it&#39;s changing faster,&#34; he notes.&lt;/p&gt;
&lt;p&gt;The evolution began in the 1930s and 40s with physical switchboards—there was no such thing as software. By the 1950s, punch cards became the norm. Boris shares a personal connection: &#34;My grandpa, actually, in the Soviet Union, he was one of the first programmers in the Soviet Union. And my mom would tell me stories about... he would bring these big stacks of punch cards home from work and she would like draw all over them with crayons.&#34;&lt;/p&gt;
&lt;p&gt;Programming evolved from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hardware (switchboards)&lt;/li&gt;
&lt;li&gt;Physical media (punch cards)&lt;/li&gt;
&lt;li&gt;Software with assembly language&lt;/li&gt;
&lt;li&gt;Higher-level languages (COBOL, C)&lt;/li&gt;
&lt;li&gt;The explosion of language families in the early 90s (Haskell, JavaScript, Java, Python)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&#34;I think nowadays, if you kind of squint, all the languages sort of look the same,&#34; Boris observes. &#34;Like when I write TypeScript, it kind of feels like writing Rust, and that kind of feels like writing Swift, and that kind of feels like writing Go. The abstractions have started to converge a bit.&#34;&lt;/p&gt;
&lt;h2&gt;The Evolution of Programming UX&lt;/h2&gt;
&lt;h3&gt;From Punch Cards to Text Editors&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/04-punch-card-ipmo29.jpg&#34; alt=&#34;IPMO29 punch card machine&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The IPMO29 was &#34;like the MacBook of the time for programming punch cards.&#34; This physical machine represented the state-of-the-art in programming interfaces before the advent of text editors.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/05-ed-first-text-editor.jpg&#34; alt=&#34;Ed - first text editor&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Then came Ed, the first text editor, invented by Ken Thompson at Bell Labs. &#34;If you open your MacBook, you can actually still type Ed. This is still distributed on Unix as part of Unix systems,&#34; Boris notes with amazement. &#34;And this is crazy, because this thing was invented like 50 years ago.&#34;&lt;/p&gt;
&lt;p&gt;Ed was incredibly primitive by today&#39;s standards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No cursor&lt;/li&gt;
&lt;li&gt;No scroll back&lt;/li&gt;
&lt;li&gt;No fancy commands&lt;/li&gt;
&lt;li&gt;No type ahead&lt;/li&gt;
&lt;li&gt;Built for teletype machines that literally printed on paper&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The GUI Revolution&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/06-smalltalk-80-gui.jpg&#34; alt=&#34;SmallTalk 80 GUI&#34; /&gt;&lt;/p&gt;
&lt;p&gt;In 1980, SmallTalk 80 represented &#34;one of the first, I think the first graphical interface for programming.&#34; Remarkably, it had features we still struggle with today: &#34;For anyone that&#39;s tried to set up live reload with React or Redux or any of this stuff, this thing had live reload in 1980, and it worked.&#34;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/07-visual-basic.jpg&#34; alt=&#34;Visual Basic&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Visual Basic in 1991 &#34;was the first code editor that introduced a graphical paradigm to the mainstream.&#34; While tools like SmallTalk existed, Visual Basic brought GUI-based programming to the masses.&lt;/p&gt;
&lt;h3&gt;Modern AI-Powered Evolution&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/08-github-copilot.jpg&#34; alt=&#34;GitHub Copilot&#34; /&gt;&lt;/p&gt;
&lt;p&gt;GitHub Copilot marked &#34;a big jump forward with single-line type ahead and then multi-line type ahead,&#34; using AI to suggest code completions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/09-devin-natural-language.jpg&#34; alt=&#34;Devin natural language programming&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Devin introduced the next major abstraction: &#34;to program, you don&#39;t have to write code, you can write natural language, and that becomes code.&#34; This concept, which developers had been trying to achieve for decades, finally became practical with advanced AI models.&lt;/p&gt;
&lt;h2&gt;Claude Code&#39;s Philosophy&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/10-claude-code-philosophy.jpg&#34; alt=&#34;Claude Code philosophy&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&#34;With all this in mind, Claude Code&#39;s approach is a little different,&#34; Boris explains. &#34;It&#39;s to start with a terminal and to give you as low-level access to the model as possible in a way that you can still be productive.&#34;&lt;/p&gt;
&lt;p&gt;The key principles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unopinionated&lt;/strong&gt;: No flashy UI or unnecessary scaffolding&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple and General&lt;/strong&gt;: Shows off the model&#39;s capabilities without getting in the way&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model-First&lt;/strong&gt;: Lets developers experience the raw power of the AI model&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&#34;We actually just don&#39;t know. Like, we don&#39;t know what the right UX is. So we&#39;re starting simple,&#34; Boris admits candidly.&lt;/p&gt;
&lt;p&gt;The guiding principle? &#34;The more general model always wins, and the model increases in capability exponentially.&#34;&lt;/p&gt;
&lt;h2&gt;Claude Code Features&lt;/h2&gt;
&lt;h3&gt;Terminal Product&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/11-terminal-product-demo.jpg&#34; alt=&#34;Terminal product demo&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The core Claude Code experience is deliberately minimal: &#34;You can install Claude Code, and then you just run claude in any terminal.&#34; It&#39;s truly unopinionated—working in iTerm2, WSL, over SSH and TMUX sessions, VS Code terminal, Cursor terminal, anywhere.&lt;/p&gt;
&lt;h3&gt;IDE Integration&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/12-ide-integration.jpg&#34; alt=&#34;IDE integration&#34; /&gt;&lt;/p&gt;
&lt;p&gt;When running Claude Code in an IDE, it provides enhanced features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Beautiful diff displays within the IDE&lt;/li&gt;
&lt;li&gt;Diagnostic information ingestion&lt;/li&gt;
&lt;li&gt;Minimal polish compared to specialized tools like Cursor or Windsurf&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&#34;This is to let you experience the model in a low-level, raw way,&#34; Boris emphasizes.&lt;/p&gt;
&lt;h3&gt;GitHub Integration&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/13-github-integration.jpg&#34; alt=&#34;GitHub integration&#34; /&gt;&lt;/p&gt;
&lt;p&gt;A few weeks before the talk, Anthropic announced GitHub integration. The setup is simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open Claude&lt;/li&gt;
&lt;li&gt;Run the slash command to install the GitHub app&lt;/li&gt;
&lt;li&gt;Pick your repository&lt;/li&gt;
&lt;li&gt;Claude runs on your compute—your data stays local&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;SDK for Custom Integrations&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/14-sdk.jpg&#34; alt=&#34;SDK&#34; /&gt;&lt;/p&gt;
&lt;p&gt;For maximum flexibility, Claude Code offers an SDK: &#34;You can build it however you want. People have built all sorts of UIs, all sorts of awesome integrations.&#34;&lt;/p&gt;
&lt;p&gt;Boris shares a personal use case: &#34;I&#39;ll take my GitHub logs, or my sorry, my GCP logs. I&#39;ll pipe it into claude -p, because it&#39;s a Unix utility, so you can pipe in, you can pipe out, and then I&#39;ll JQ the result.&#34;&lt;/p&gt;
&lt;h2&gt;Tips for Using Claude Code Effectively&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/14-tips-for-using.jpg&#34; alt=&#34;Tips for using Claude Code&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Boris shares practical advice for getting the most out of Claude Code:&lt;/p&gt;
&lt;h3&gt;1. Start with Code-Based Q&amp;amp;A&lt;/h3&gt;
&lt;p&gt;&#34;At Anthropic, we teach Claude Code to every engineer on day one, and it shortened onboarding times from like two or three weeks to like two days.&#34;&lt;br /&gt;
Engineers can simply ask Claude questions about the codebase.&lt;/p&gt;
&lt;h3&gt;2. Teach Claude to Use Your Tools&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/15-teach-claude-your-tools.jpg&#34; alt=&#34;Teach Claude your tools&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&#34;With every IDE, there&#39;s sort of like a plug-in ecosystem... For this new kind of coding tool, it can just use all your tools.&#34;&lt;br /&gt;
The process is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Give it bash tools or MCP tools&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;--help&lt;/code&gt; on a CLI tool&lt;/li&gt;
&lt;li&gt;Put the information in claude.md&lt;/li&gt;
&lt;li&gt;Claude now knows how to use the tool&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Effective Workflows&lt;/h3&gt;
&lt;p&gt;Traditional coding tools focused on writing code, but AI-powered tools do much more:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exploration and Planning&lt;/strong&gt;: Have Claude explore and make a plan before writing code. Use extended thinking when there&#39;s substantial context.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test-Driven Development (TDD)&lt;/strong&gt;: &#34;I know I try to use TDD. It&#39;s like, it&#39;s pretty hard to use in practice. But I think now with coding tools, it actually works really well.&#34;&lt;br /&gt;
The workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tell Claude to write tests first&lt;/li&gt;
&lt;li&gt;Explicitly state the tests won&#39;t pass yet&lt;/li&gt;
&lt;li&gt;Commit the tests&lt;/li&gt;
&lt;li&gt;Then write the code and commit&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Provide Iteration Targets&lt;/strong&gt;: &#34;If Claude has a target to iterate against, it can do much better.&#34;&lt;br /&gt;
This could be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unit tests&lt;/li&gt;
&lt;li&gt;Integration tests&lt;/li&gt;
&lt;li&gt;Screenshots from iOS simulator or Puppeteer&lt;/li&gt;
&lt;li&gt;Even camera feedback from 3D printers!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;New Features: Plan Mode&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/16-plan-mode-feature.jpg&#34; alt=&#34;Plan Mode feature&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&#34;Today we launched plan mode in Claude Code,&#34; Boris announces. By hitting Shift+Tab, Claude switches to Plan Mode where it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates a plan for your request&lt;/li&gt;
&lt;li&gt;Waits for approval before executing&lt;/li&gt;
&lt;li&gt;Allows you to review and modify the approach&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Power of Context&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/claude-code-the-evolution-of-agentic-coding/17-more-context-better.jpg&#34; alt=&#34;More context better&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&#34;Give Claude more context,&#34; Boris emphasizes. Several methods make this easy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;claude.md files&lt;/strong&gt;: Place in your home folder or project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slash commands&lt;/strong&gt;: Put markdown files in &lt;code&gt;.claude/slash-commands&lt;/code&gt; folders&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory function&lt;/strong&gt;: Type &lt;code&gt;#&lt;/code&gt; to ask Claude to memorize something&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&#34;You can tell this is still pretty rough. This is our first version, but it&#39;s the first version that works,&#34; Boris acknowledges, inviting feedback on the UX.&lt;/p&gt;
&lt;h2&gt;The Future: Multi-Claude Workflows&lt;/h2&gt;
&lt;p&gt;During the Q&amp;amp;A, an interesting question arose about managing multiple Claude instances. Power users are already &#34;multi-Clauding&#34;—running several terminal tabs with different Claude instances working in parallel on different parts of a codebase or different work trees.&lt;/p&gt;
&lt;p&gt;&#34;If you do want to coordinate, the best way is just ask them to write to a markdown file, and that&#39;s it,&#34; Boris suggests for inter-Claude communication.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Claude Code represents a fundamental shift in how we think about AI-assisted programming. Rather than trying to build the perfect IDE or the ultimate coding assistant, it provides direct, unopinionated access to increasingly powerful AI models. This approach acknowledges a crucial reality: in a world where AI capabilities are growing exponentially, the most valuable tool might be the simplest one—one that gets out of the way and lets developers experiment, learn, and discover new ways of working with AI.&lt;/p&gt;
&lt;p&gt;As Boris concludes, the evolution of programming—from punch cards to natural language—is accelerating. Claude Code positions itself not as the final answer, but as a flexible foundation for whatever comes next. In the race between exponentially improving models and the products built to harness them, sometimes the winning move is to build less, not more.&lt;/p&gt;
&lt;p&gt;The message is clear: the future of coding isn&#39;t just about better tools—it&#39;s about fundamentally rethinking the relationship between human creativity and AI capability. Claude Code is an invitation to join that exploration.&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Claude Code shortened onboarding times at Anthropic from 2-3 weeks to just 2 days&lt;/li&gt;
&lt;li&gt;The tool is intentionally &#34;unopinionated&#34; - it works in any terminal environment&lt;/li&gt;
&lt;li&gt;Power users are already &#34;multi-Clauding&#34; - running multiple instances in parallel&lt;/li&gt;
&lt;li&gt;Plan Mode (Shift+Tab) was launched the day of this talk&lt;/li&gt;
&lt;li&gt;Claude Code can be taught to use any CLI tool by showing it the --help output&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;YouTube Description&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A ten thousand foot view of the coding space, the UX of coding, and the Claude Code team&#39;s approach.&lt;/p&gt;
&lt;p&gt;About Boris Chemy
Created Claude Code. Member of Technical Staff @Anthropic. Prev: Principal Engineer @Meta, Architect @Coatue. Author, OReilly&#39;s Programming TypeScript.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Chapters on YouTube&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=0&#34;&gt;0:00&lt;/a&gt; - Introduction &amp;amp; the exponential model challenge&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=49&#34;&gt;0:49&lt;/a&gt; - Getting started with Claude Code&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=85&#34;&gt;1:25&lt;/a&gt; - Evolution of programming languages&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=225&#34;&gt;3:45&lt;/a&gt; - Evolution of programming UX&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=270&#34;&gt;4:30&lt;/a&gt; - IPMO29: The MacBook of punch cards&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=300&#34;&gt;5:00&lt;/a&gt; - Ed: The first text editor&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=380&#34;&gt;6:20&lt;/a&gt; - SmallTalk 80 &amp;amp; GUI revolution&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=430&#34;&gt;7:10&lt;/a&gt; - Visual Basic brings GUI mainstream&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=480&#34;&gt;8:00&lt;/a&gt; - GitHub Copilot &amp;amp; AI evolution&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=525&#34;&gt;8:45&lt;/a&gt; - Devin &amp;amp; natural language programming&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=600&#34;&gt;10:00&lt;/a&gt; - Claude Code&#39;s philosophy&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=690&#34;&gt;11:30&lt;/a&gt; - Terminal product demo&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=735&#34;&gt;12:15&lt;/a&gt; - IDE integration&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=765&#34;&gt;12:45&lt;/a&gt; - GitHub integration&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=795&#34;&gt;13:15&lt;/a&gt; - SDK for custom integrations&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=840&#34;&gt;14:00&lt;/a&gt; - Tips: Code-based Q&amp;amp;A&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=870&#34;&gt;14:30&lt;/a&gt; - Teaching Claude your tools&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=900&#34;&gt;15:00&lt;/a&gt; - Effective workflows &amp;amp; TDD&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=945&#34;&gt;15:45&lt;/a&gt; - Plan Mode launch&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=975&#34;&gt;16:15&lt;/a&gt; - The power of context&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://youtu.be/Lue8K2jqfKk?t=1000&#34;&gt;16:40&lt;/a&gt; - Q&amp;amp;A: Multi-Claude workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;References:&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=Lue8K2jqfKk&#34;&gt;Claude Code &amp;amp; The Evolution of Agentic Coding - Boris Cherny (YouTube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://x.com/bcherny/status/1932652135549972935&#34;&gt;Boris Cherny on X (Twitter)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.google.com/presentation/d/1FGNy3wHoXEnVpgOTB9rtgdyjaQvEVHmhxGhHoGIPX98/edit?slide=id.p#slide=id.p&#34;&gt;Talk Slides (Google Docs)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.anthropic.com/claude-code&#34;&gt;Claude Code - Anthropic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.anthropic.com/en/docs/claude-code/overview&#34;&gt;Claude Code Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.anthropic.com/&#34;&gt;Anthropic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Talk: Agentic Coding - The Future of Software Development with Agents</title>
    <id>https://ikyle.me/blog/2025/agentic-coding-the-future-of-software-development-with-agents</id>
    <updated>2025-07-04T02:10:28.919128+00:00</updated>
    <published>2025-07-04T02:10:28.919115+00:00</published>
    <link href="https://ikyle.me/blog/2025/agentic-coding-the-future-of-software-development-with-agents" />
    <summary type="text">Talk by Flask creator on agentic coding - how AI agents actively collaborate with programmers, covering Claude Code, new development practices, and tooling</summary>
    <content type="html">&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/agentic-coding-the-future-of-software-development-with-agents/poster-720p.jpg&#34; alt=&#34;Agentic Coding Poster&#34; title=&#34;Agentic Coding - The Future of Software Development with Agents&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video&lt;/strong&gt;: &lt;a href=&#34;https://www.youtube.com/watch?v=nfOVgz_omlU&#34;&gt;https://www.youtube.com/watch?v=nfOVgz_omlU&lt;/a&gt;&lt;br /&gt;
&lt;strong&gt;Title&lt;/strong&gt;: Agentic Coding - The Future of Software Development with Agents&lt;br /&gt;
&lt;strong&gt;Speaker&lt;/strong&gt;: Armin Ronacher (Flask creator, former Sentry engineer)&lt;br /&gt;
&lt;strong&gt;Duration&lt;/strong&gt;: 37 minutes&lt;br /&gt;
&lt;strong&gt;Topic&lt;/strong&gt;: Real-time AI agent collaboration in software development&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;Armin Ronacher, the creator of &lt;a href=&#34;https://flask.palletsprojects.com/en/stable/quickstart/#a-minimal-application&#34;&gt;Flask&lt;/a&gt;, has been one of the people &lt;a href=&#34;https://x.com/mitsuhiko/status/1939105797448872265&#34;&gt;diving headfirst into LLM agentic programming recently&lt;/a&gt; and has put together a talk going over some of the main insights he&#39;s learnt from working with these tools over the last few months.&lt;/p&gt;
&lt;p&gt;The agents are now good enough to collaborate. Running independently for hours on tasks, changing how we approach software development.&lt;/p&gt;
&lt;p&gt;One key takeaway, which I&#39;ve also found recently in my own experience is: &lt;a href=&#34;https://docs.anthropic.com/en/docs/claude-code/overview&#34;&gt;Claude Code&lt;/a&gt;.&lt;br /&gt;
Claude Code is finally over the threshold of being good enough, and reliable enough, to fundamentally change how you use it.&lt;/p&gt;
&lt;p&gt;The talk goes over how to make use of these new tools, where they are strongest, and where they are still lacking. As well as some tips on how to use them effectively.&lt;/p&gt;
&lt;h1&gt;Talk Chapters&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=0&#34;&gt;00:00&lt;/a&gt; - Introduction &amp;amp; Speaker Background&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=162&#34;&gt;02:42&lt;/a&gt; - What Is Agentic Coding?&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=316&#34;&gt;05:16&lt;/a&gt; - Why Now? The Perfect Storm&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=457&#34;&gt;07:37&lt;/a&gt; - Claude Code vs. Cursor&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=817&#34;&gt;13:37&lt;/a&gt; - Practical Usage Beyond Programming&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=947&#34;&gt;15:47&lt;/a&gt; - Programming Language Recommendations&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=1086&#34;&gt;18:06&lt;/a&gt; - Development Environment Optimization&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=1250&#34;&gt;20:50&lt;/a&gt; - MCP (Model Context Protocol) Discussion&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=1438&#34;&gt;23:58&lt;/a&gt; - Context Management &amp;amp; Sub-agents&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=1589&#34;&gt;26:29&lt;/a&gt; - Advanced Development Patterns&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=1925&#34;&gt;32:05&lt;/a&gt; - Creative Use Cases &amp;amp; Examples&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/nfOVgz_omlU?t=2188&#34;&gt;36:28&lt;/a&gt; - Future Vision &amp;amp; Conclusion&lt;/p&gt;
&lt;h2&gt;Talk Summary&lt;/h2&gt;
&lt;h3&gt;Introduction &amp;amp; The Addiction Factor (0:00-2:42)&lt;/h3&gt;
&lt;p&gt;Armin admits he&#39;s fallen down a rabbit hole of AI experimentation since May. (The talk is recorded at midnight, emphasizing just how consuming this technology has become)&lt;/p&gt;
&lt;h3&gt;What Is Agentic Coding? (2:42-5:04)&lt;/h3&gt;
&lt;p&gt;Agentic coding represents a fundamental shift from autocomplete tools like Cursor or GitHub Copilot to true real-time collaboration between humans and AI agents. These agents actively participate in the entire coding process, can run for hours on complex tasks through sub-agents, and manage context effectively.&lt;/p&gt;
&lt;h3&gt;Why Now? (5:04-7:35)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Anthropic&#39;s Sonnet 4 and Opus 4 models have been specifically trained on tool usage&lt;/li&gt;
&lt;li&gt;Claude Code is a step above everything else at the moment.&lt;/li&gt;
&lt;li&gt;The subscription pricing model ($100-200) makes these tools far more cost-effective than paying for API tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Claude Code vs. Cursor (7:35-13:34)&lt;/h3&gt;
&lt;p&gt;Claude Code supports longer sessions, and is more ready to read files and use tools. Unlike Cursor&#39;s editor-focused approach, Claude Code is a terminal interface, so supports, SSH access, composition and remote access easier. The terminal-based approach enables better experimentation and even nesting agents within each other.&lt;/p&gt;
&lt;h3&gt;Practical Usage Philosophy (13:34-15:39)&lt;/h3&gt;
&lt;p&gt;Armin uses Claude Code far beyond programming - for CI setup, machine configuration, creating tools on the spot, and even web browsing. The trust factor is crucial: Claude Code feels safer than Cursor, with fewer accidental deletions and better protective measures.&lt;/p&gt;
&lt;h3&gt;Programming Language Recommendations (15:39-18:06)&lt;/h3&gt;
&lt;p&gt;Simple project structures, well-known languages and frameworks work best with agents. Go, PHP, and &#34;basic Python&#34; (simple scripts with minimal dependencies) are ideal because they have less ecosystem churn, clear patterns, and rely heavily on standard libraries. Using long, descriptive function names to reduce AI confusion and prevent code duplication across the codebase.&lt;/p&gt;
&lt;h3&gt;Development Environment Optimization (18:06-20:50)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The quality of your development environment is the primary factor determining agent success.&lt;/li&gt;
&lt;li&gt;Agents will misuse tools, so those tools must handle misuse gracefully with clear error messages.&lt;/li&gt;
&lt;li&gt;Speed matters - agents will abandon slow tools and stop using them entirely.&lt;/li&gt;
&lt;li&gt;Agents want simple log files and good exceptions, not complex telemetry setups.&lt;/li&gt;
&lt;li&gt;Providing ways for agents to create scratch tools and throwaway code is essential for experimentation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;MCP (Model Context Protocol) Reality Check (20:50-23:58)&lt;/h3&gt;
&lt;p&gt;Command-line tools are more effective than MCPs.&lt;br /&gt;
Armin only uses one MCP (&lt;a href=&#34;https://github.com/microsoft/playwright-mcp&#34;&gt;Playwright&lt;/a&gt;) and relies on command-line tools for everything else.&lt;/p&gt;
&lt;p&gt;CLI tools win because they can be embedded in scripts, and agents understand code writing better than MCP interactions.&lt;br /&gt;
MCPs create context bloat and are annoying for humans to use and debug.&lt;/p&gt;
&lt;p&gt;He&#39;s since written a blog post exploring this point more thoroughly.&lt;br /&gt;
&lt;a href=&#34;https://lucumr.pocoo.org/2025/7/3/tools/?newcard&#34;&gt;&#34;Tools: Code Is All You Need&#34;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Context Management Strategies (23:58-26:29)&lt;/h3&gt;
&lt;p&gt;Effective context management requires custom tools like &#34;MakeGoMethods&#34; that summarize entire codebases efficiently.&lt;br /&gt;
A practical approach is providing access to the last 20 lines of logs with clear instructions for getting more detail when needed.&lt;br /&gt;
Sub-agents can break down large tasks and run for 12+ hours, but requires careful planning, then creating markdown plans for step-by-step execution.&lt;br /&gt;
Context rot - where failed attempts accumulate - causes backtracking and confusion, so aborting when running low on context is often better than letting the system compact.&lt;/p&gt;
&lt;h3&gt;Advanced Development Patterns (26:29-32:05)&lt;/h3&gt;
&lt;p&gt;Unified log files (forwarding browser console logs and server logs into the same file) are great for the LLM to understand what is happening.&lt;/p&gt;
&lt;h3&gt;Creative Use Cases (32:05-37:19)&lt;/h3&gt;
&lt;p&gt;Claude Code excels at diverse tasks: calling Gemini CLI for PDF processing and summarization, automatically downloading videos and integrating them into presentations, automating Google Chrome to create marketplace listings with proper descriptions and pricing (using Playwright), and performing system administration like Git reconfiguration.&lt;/p&gt;
&lt;p&gt;The talk itself was put together using Claude code.&lt;br /&gt;
It&#39;s an opinion that&#39;s shared by others: &lt;a href=&#34;https://steipete.me/posts/2025/claude-code-is-my-computer&#34;&gt;&#34;Claude Code is My Computer&#34;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Key Takeaways&lt;/h2&gt;
&lt;h3&gt;For Developers&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start Simple&lt;/strong&gt;: Go, PHP, and basic Python work best with agents&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimize Your Dev Environment&lt;/strong&gt;: Agent success depends on tool quality and speed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embrace Terminal-Based Tools&lt;/strong&gt;: More flexible than editor integrations for experimentation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context Management is Important&lt;/strong&gt;: Use sub-agents, CLI tools, and avoid context rot&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Tools Mentioned&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://docs.anthropic.com/en/docs/claude-code&#34;&gt;Claude Code&lt;/a&gt;&lt;/strong&gt;: Primary agentic coding tool discussed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://cursor.com/&#34;&gt;Cursor&lt;/a&gt;&lt;/strong&gt;: Great IDE integration, but much more limited agent mode that also goes off track more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/opencode-ai/opencode&#34;&gt;OpenCode&lt;/a&gt;&lt;/strong&gt;: Alternative tool mentioned&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/google-gemini/gemini-cli&#34;&gt;Gemini CLI&lt;/a&gt;&lt;/strong&gt;: Used for specific tasks like PDF processing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://playwright.dev/&#34;&gt;Playwright&lt;/a&gt;&lt;/strong&gt;: Browser automation tool, only MCP the speaker actively uses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The talk emphasizes that while we&#39;re in the early days, the combination of advanced LLMs with agentic workflows represents a shift in how software development can be approached.&lt;/p&gt;
&lt;h1&gt;References&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=nfOVgz_omlU&#34;&gt;YouTube Video: Agentic Coding - The Future of Software Development with Agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.anthropic.com/en/docs/claude-code&#34;&gt;Claude Code Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://steipete.me/posts/2025/claude-code-is-my-computer&#34;&gt;Claude Code is My Computer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lucumr.pocoo.org/2025/6/12/agentic-coding/&#34;&gt;Agentic Coding Recommendations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lucumr.pocoo.org/2025/7/3/tools/&#34;&gt;Tools: Code Is All You Need&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Talk: Andrej Karpathy: Software Is Changing (Again)</title>
    <id>https://ikyle.me/blog/2025/andrej-karpathy-software-is-changing-again</id>
    <updated>2025-06-21T02:02:15.408206+00:00</updated>
    <published>2025-06-21T02:02:15.408192+00:00</published>
    <link href="https://ikyle.me/blog/2025/andrej-karpathy-software-is-changing-again" />
    <summary type="text">Andrej Karpathy&#39;s keynote at AI Startup School in San Francisco about how LLMs are introducing a 3rd generation of software</summary>
    <content type="html">&lt;p&gt;&lt;strong&gt;Andrej Karpathy: Software Is Changing (Again)&lt;/strong&gt;&lt;br /&gt;
Andrej Karpathy&#39;s keynote at AI Startup School in San Francisco.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/andrej-karpathy-software-is-changing-again/software-1-2-3.jpeg&#34; alt=&#34;Software 1.0, 2.0 and 3.0&#34; title=&#34;Scaling Al Translations&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=LCEmiRjPEtQ&#34;&gt;YouTube Video (39m)&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;Andrej Karpathy is a machine learning research scientist and a founding member at OpenAI, former Sr. Director of AI at Tesla.&lt;/p&gt;
&lt;p&gt;Earlier this year he gave a fascinating the keynote talk at Y Combinator&#39;s &#34;AI Startup School in San Francisco&#34; where he talks about the concept of &#34;software 3.0&#34;.&lt;/p&gt;
&lt;p&gt;The idea is software 1.0 was normal code.&lt;br /&gt;
Software 2.0 is machine learning models, which have started being deployed on mass in the last few years.&lt;br /&gt;
And now software 3.0 with LLMs. An entire new generation of software.&lt;/p&gt;
&lt;h1&gt;Talk Chapters&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://x.com/karpathy/status/1935518272667217925&#34;&gt;From Andrej Karpathy&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=0&#34;&gt;0:00&lt;/a&gt; Imo fair to say that software is changing quite fundamentally again. LLMs are a new kind of computer, and you program them &lt;em&gt;in English&lt;/em&gt;. Hence I think they are well deserving of a major version upgrade in terms of software.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=366&#34;&gt;6:06&lt;/a&gt; LLMs have properties of utilities, of fabs, and of operating systems =&amp;gt; New LLM OS, fabbed by labs, and distributed like utilities (for now). Many historical analogies apply - imo we are computing circa ~1960s.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=879&#34;&gt;14:39&lt;/a&gt; LLM psychology: LLMs = &#34;people spirits&#34;, stochastic simulations of people, where the simulator is an autoregressive Transformer. Since they are trained on human data, they have a kind of emergent psychology, and are simultaneously superhuman in some ways, but also fallible in many others. Given this, how do we productively work with them hand in hand?&lt;/p&gt;
&lt;p&gt;Switching gears to opportunities...&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=1096&#34;&gt;18:16&lt;/a&gt; LLMs are &#34;people spirits&#34; =&amp;gt; can build partially autonomous products.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=1745&#34;&gt;29:05&lt;/a&gt; LLMs are programmed in English =&amp;gt; make software highly accessible! (yes, vibe coding)&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=2016&#34;&gt;33:36&lt;/a&gt; LLMs are new primary consumer/manipulator of digital information (adding to GUIs/humans and APIs/programs) =&amp;gt; Build for agents!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Talk Summary:&lt;/h1&gt;
&lt;h2&gt;Overall Talk Topic&lt;/h2&gt;
&lt;p&gt;Andrej Karpathy presents a case that software is undergoing a foundational transformation. Large Language Models (LLMs) represent a new kind of computer—programmable in natural language. This shift is significant enough to justify calling it a major new version of software: “Software 3.0.” The talk explores the architecture of LLMs, their emergent psychology, the nature of current opportunities, and the best strategies for building with and for them.&lt;/p&gt;
&lt;h2&gt;Section 1: Software is Fundamentally Changing (0:49)&lt;/h2&gt;
&lt;p&gt;LLMs are not just models—they are a new type of computer. They are programmable in English and offer capabilities that differ fundamentally from traditional imperative or neural-network-based software. Given this new paradigm, Karpathy suggests it’s appropriate to upgrade our versioning of software entirely. Software 1.0 was classical logic, Software 2.0 was neural networks with learned weights, and Software 3.0 is built with LLMs using natural language as the programming layer.&lt;/p&gt;
&lt;h2&gt;Section 2: LLMs as Utilities, Fabs, and Operating Systems (6:06)&lt;/h2&gt;
&lt;p&gt;Karpathy describes LLMs as being akin to utilities (like electricity), chip fabs (expensive to produce, cheap to use), and operating systems (platforms with APIs and abstractions). Like 1960s computing, most people now use thin clients to access expensive centralized models. While they are currently cloud-hosted utilities, a personal computing model may re-emerge as smaller models become locally deployable.&lt;/p&gt;
&lt;h2&gt;Section 3: LLM Psychology (14:39)&lt;/h2&gt;
&lt;p&gt;LLMs behave like &#34;people spirits&#34;—simulations of human minds with a psychology shaped by their training data. They possess strengths like encyclopedic recall and creative problem-solving, but also flaws such as hallucinations, inconsistency, and poor memory. They lack persistence and self-awareness. Effective collaboration with LLMs requires tools that accommodate their abilities while mitigating their weaknesses, especially with tight generate/verify cycles and human oversight.&lt;/p&gt;
&lt;h2&gt;Section 4: Opportunities: Partially Autonomous Products (18:16)&lt;/h2&gt;
&lt;p&gt;Because LLMs simulate people, they can drive new forms of human-computer interaction. Products like Cursor and Perplexity wrap LLMs in interfaces that provide automated context, diff-based UIs for human review, and autonomy sliders that balance between user control and model initiative. The most successful applications will support a human-in-the-loop dynamic, allowing LLMs to act semi-independently within guardrails.&lt;/p&gt;
&lt;h2&gt;Section 5: English as Code – Natural Language Interfaces (29:05)&lt;/h2&gt;
&lt;p&gt;Programming LLMs in English democratizes access to software creation. Karpathy describes how “vibe coding” and “byte coding” let even non-programmers generate useful applications. However, core infrastructure (e.g., payments, auth) remains complex. While building an iOS app or web tool is now faster than ever, operations are still a bottleneck, suggesting where further automation or tooling improvements could help.&lt;/p&gt;
&lt;h2&gt;Section 6: Building for Agents (33:36)&lt;/h2&gt;
&lt;p&gt;LLMs are now a primary manipulator of digital content, joining humans (via GUIs) and traditional programs (via APIs). Developers should design with agents in mind: create markdown-readable interfaces, machine-consumable documentation (like &lt;code&gt;llms.txt&lt;/code&gt;), and avoid GUI-only affordances. Early examples like &lt;code&gt;git.ingest&lt;/code&gt; and DeepWiki reveal how small design tweaks can expose functionality to agents and unlock powerful behaviors.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;LLMs mark the beginning of a new software paradigm. Though they are not perfect, they are powerful and increasingly central to both consumer and developer workflows. Over the next decade, software will shift toward partially autonomous, LLM-driven agents, and the developers who embrace Software 3.0’s strengths—while designing robust human-AI workflows—will define the next era of computing.&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Outages at leading LLM providers act like an “intelligence brownout,” instantly lowering global productivity.&lt;/li&gt;
&lt;li&gt;Unlike electricity or GPS, LLMs diffused &lt;em&gt;consumer-first&lt;/em&gt;; enterprises and governments are still catching up.&lt;/li&gt;
&lt;li&gt;The ability to run small models on a Mac Mini hints at a coming personal-compute era once inference costs fall.&lt;/li&gt;
&lt;li&gt;Tesla’s experience shows entire swaths of C++ code can be deleted as neural nets, and soon prompts, absorb that functionality.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;YouTube Description&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Drawing on his work at Stanford, OpenAI, and Tesla, Andrej sees a shift underway. Software is changing, again. We’ve entered the era of “Software 3.0,” where natural language becomes the new programming interface and models do the rest.&lt;/p&gt;
&lt;p&gt;He explores what this shift means for developers, users, and the design of software itself— that we&#39;re not just using new tools, but building a new kind of computer.&lt;/p&gt;
&lt;p&gt;More content from &lt;a href=&#34;https://x.com/karpathy&#34;&gt;Andrej&lt;/a&gt;: / &lt;a href=&#34;https://www.youtube.com/channel/UCXUPKJO5MZQN11PqgIvyuvQ&#34;&gt;@andrejkarpathy&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Chapters on YouTube&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=0&#34;&gt;00:00&lt;/a&gt; - Intro&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=85&#34;&gt;01:25&lt;/a&gt; - Software evolution: From 1.0 to 3.0&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=280&#34;&gt;04:40&lt;/a&gt; - Programming in English: Rise of Software 3.0&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=370&#34;&gt;06:10&lt;/a&gt; - LLMs as utilities, fabs, and operating systems&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=664&#34;&gt;11:04&lt;/a&gt; - The new LLM OS and historical computing analogies&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=879&#34;&gt;14:39&lt;/a&gt; - Psychology of LLMs: People spirits and cognitive quirks&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=1102&#34;&gt;18:22&lt;/a&gt; - Designing LLM apps with partial autonomy&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=1420&#34;&gt;23:40&lt;/a&gt; - The importance of human-AI collaboration loops&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=1560&#34;&gt;26:00&lt;/a&gt; - Lessons from Tesla Autopilot &amp;amp; autonomy sliders&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=1672&#34;&gt;27:52&lt;/a&gt; - The Iron Man analogy: Augmentation vs. agents&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=1746&#34;&gt;29:06&lt;/a&gt; - Vibe Coding: Everyone is now a programmer&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=2019&#34;&gt;33:39&lt;/a&gt; - Building for agents: Future-ready digital infrastructure&lt;br /&gt;
&lt;a href=&#34;https://youtu.be/LCEmiRjPEtQ?si=1bmgeHiCwnf9hAjd&amp;amp;t=2294&#34;&gt;38:14&lt;/a&gt; - Summary: We’re in the 1960s of LLMs — time to build&lt;/p&gt;
&lt;h1&gt;References:&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=LCEmiRjPEtQ&#34;&gt;Andrej Karpathy: Software Is Changing (Again) - (YouTube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://x.com/ycombinator/status/1935496106957488566&#34;&gt;Andrej Karpathy: Software Is Changing (Again) - (Y Combinator, on x)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://x.com/karpathy/status/1935518272667217925&#34;&gt;First Andrej Karpathy Tweet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://x.com/karpathy/status/1935519334123848101&#34;&gt;Second Andrej Karpathy Tweet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://drive.google.com/file/d/1a0h1mkwfmV2PlekxDN8isMrDA5evc4wW/view&#34;&gt;Slides Keynote&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://drive.google.com/file/d/1kF3FWzLPCjJH-3m4jP3oteINX_7hA6_t/view&#34;&gt;Slides PDF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://karpathy.medium.com/software-2-0-a64152b37c35&#34;&gt;Software 2.0 blog post from 2017&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://karpathy.bearblog.dev/power-to-the-people/&#34;&gt;How LLMs flip the script on technology diffusion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://karpathy.bearblog.dev/vibe-coding-menugen/&#34;&gt;Vibe coding MenuGen (retrospective)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/channel/UCXUPKJO5MZQN11PqgIvyuvQ&#34;&gt;His YouTube Channel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Talk: Instagram Video Translation System Overview Talk by Meta</title>
    <id>https://ikyle.me/blog/2025/instagram-video-translation-system-overview-talk-by-meta</id>
    <updated>2025-05-22T01:01:55.901480+00:00</updated>
    <published>2025-05-22T01:01:55.901480+00:00</published>
    <link href="https://ikyle.me/blog/2025/instagram-video-translation-system-overview-talk-by-meta" />
    <summary type="text">Scaling AI Translations at Meta: tackling latency and media processing challenges (March 2025)</summary>
    <content type="html">&lt;p&gt;I came across a really interesting tech talk from an engineer at Meta, going over how the video translation system being implemented on Instagram reels works.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/instagram-video-translation-system-overview-talk-by-meta/poster.jpeg&#34; alt=&#34;Scaling Al Translations Post Image&#34; title=&#34;Scaling Al Translations&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video&lt;/strong&gt;: &lt;a href=&#34;https://www.youtube.com/watch?v=wJOjVr2_WH0&#34;&gt;https://www.youtube.com/watch?v=wJOjVr2_WH0&lt;/a&gt;&lt;br /&gt;
&lt;strong&gt;Title&lt;/strong&gt;: Scaling AI translations at Meta: tackling latency and media processing challenges&lt;br /&gt;
&lt;strong&gt;Speaker&lt;/strong&gt;: Jordi Cenzano (Software Engineer, Meta)&lt;br /&gt;
&lt;strong&gt;Topic&lt;/strong&gt;: Meta AI’s media translation system with audio and video (lip-sync) translations&lt;br /&gt;
&lt;strong&gt;Conference&lt;/strong&gt;: 2025/03/27 MAD Video Tech.&lt;/p&gt;
&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;This talk from an engineer at meta goes through how they built the auto-video translation feature built into Instagram.&lt;/p&gt;
&lt;p&gt;The primary model used is the open source &lt;a href=&#34;https://huggingface.co/facebook/seamless-m4t-v2-large&#34;&gt;seamless-m4t&lt;/a&gt; model. But the overall feature requires pre and post processing with other tools and models to get everything feature complete.&lt;/p&gt;
&lt;h2&gt;Models Used&lt;/h2&gt;
&lt;p&gt;The pipeline uses more than 10 models, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Voice-Ambient Audio Separation Model&lt;/strong&gt;&lt;br /&gt;
Separates speech from background noise for clean source audio.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Language Detection Model&lt;/strong&gt;&lt;br /&gt;
Is the audio even in a supported language?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sentence Segmentation Model&lt;/strong&gt;&lt;br /&gt;
Seamless is designed for short 10-15s audio clips, so the input audio needs to be broken up into small chunks, which still contain enough context for an accurate translation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Seamless audio to audio translation model&lt;/strong&gt;&lt;br /&gt;
Does the actual translation from the source language into the target language.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LipSync Model&lt;/strong&gt;&lt;br /&gt;
Takes translated clean audio + original video to generate lip-synced video.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toxicity Filter / Hallucination Detection Model&lt;/strong&gt;&lt;br /&gt;
Attempts to detect if the translation is more toxic than the input was, and so the translation might have been incorrect.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The talks outlines that more than 10 ML models are used, though only Seamless is named directly.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Translation Pipeline Overview&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Upload&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User uploads video via Instagram or Facebook.&lt;/li&gt;
&lt;li&gt;Video is uploaded to Meta’s distributed storage system.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Audio Translation&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Audio Extraction &amp;amp; Decoded into the correct format for processing (AAC → PCM)&lt;/li&gt;
&lt;li&gt;Pre-Processing:&lt;br /&gt;
•	Clean speech extraction (background noise removal)&lt;br /&gt;
•	Language detection&lt;br /&gt;
•	Sentence segmentation&lt;/li&gt;
&lt;li&gt;Seamless Model Audio Translation:&lt;br /&gt;
•	Input: short PCM audio segments&lt;br /&gt;
•	Output: translated PCM audio&lt;/li&gt;
&lt;li&gt;Post-Processing:&lt;br /&gt;
•	Re-align segments (time alignment across languages)&lt;br /&gt;
•	(Implied they also adjust the video slightly if more time is needed in the target language?)&lt;br /&gt;
•	Recombine segments&lt;br /&gt;
•	Re-add ambient noise&lt;br /&gt;
•	Re-encode audio (PCM → AAC)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Video (LipSync)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Using clean translated audio and original video&lt;/li&gt;
&lt;li&gt;LipSync Model:&lt;br /&gt;
•	Generates new video with synchronized lip movement synced to the new audio&lt;/li&gt;
&lt;li&gt;Post-process:&lt;br /&gt;
•	Overlay visible watermark (e.g., visible tag for AI-generated content)&lt;br /&gt;
•	Encode and mux final audio/video&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Safety and Approval&lt;br /&gt;
•	Toxicity filter checks both input and output&lt;br /&gt;
•	Invisible watermarking&lt;br /&gt;
•	Optional user approval flow before publishing&lt;br /&gt;
•	Feedback system for corrections/removals&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Playback Logic&lt;br /&gt;
•	Language-aware serving:&lt;br /&gt;
•	Prefers original if user device matches&lt;br /&gt;
•	Auto-serves translated/lip-synced audio and video content otherwise&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Future Improvements&lt;/h1&gt;
&lt;p&gt;The talk also mentioned various points which need improvement.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add support for more languages (Seamless supports over 100 languages, but the overall system is more limited)&lt;/li&gt;
&lt;li&gt;Support multi-speaker content (currently this system only supports a single speaker)&lt;/li&gt;
&lt;li&gt;Improve speaker voice cloning (matching the emotion and intonation of the original speaker)&lt;/li&gt;
&lt;li&gt;Better support videos with background music (auto detect which song is playing to help separate and re-add the audio)&lt;/li&gt;
&lt;li&gt;Improve latency and real-time deployment (make it faster)&lt;/li&gt;
&lt;li&gt;Enhance evaluation via better metrics &amp;amp; automation&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;Given the base audio to audio translation model has been open source for some time the main new information in the talk is how such a model is productised and the amount of pre and post processing needed to deploy the model in a real end user product.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Binary Quantized Embeddings</title>
    <id>https://ikyle.me/blog/2025/binary-quantized-embeddings</id>
    <updated>2025-05-17T05:37:56.798085+00:00</updated>
    <published>2025-05-17T05:37:56.798085+00:00</published>
    <link href="https://ikyle.me/blog/2025/binary-quantized-embeddings" />
    <summary type="text">How to use binary quantized embeddings for semantic text search</summary>
    <content type="html">&lt;h1&gt;How to use binary quantized embeddings for semantic text search&lt;/h1&gt;
&lt;p&gt;Have you ever wanted a search that didn&#39;t just matched keywords, but searched based on the word or sentence meaning, returning sentences that meant the same, even if spelt slightly differently?&lt;br /&gt;
Let&#39;s explore how to create a simple retrieval system using binary quantized embeddings for efficient semantic search.&lt;/p&gt;
&lt;p&gt;This sort of system is typically used for a Retrieval Augmented Generation (RAG) system. Where you are pre-preparing a prompt to send to an LLM.&lt;/p&gt;
&lt;h2&gt;What are we building?&lt;/h2&gt;
&lt;p&gt;We&#39;re going to build a lookup system for RAG that can:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Convert text into compact binary embeddings&lt;/li&gt;
&lt;li&gt;Store them efficiently in SQLite&lt;/li&gt;
&lt;li&gt;Perform very fast semantic searches&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&#39;s start by understanding the core components we&#39;ll be using.&lt;/p&gt;
&lt;h2&gt;What are embeddings?&lt;/h2&gt;
&lt;p&gt;Embeddings are numerical representations of text that capture semantic meaning. When we convert text to embeddings, similar meanings end up closer together in the embedding space, enabling semantic search by finding embeddings which are &#34;near&#34; our reference embedding we are searching for.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;# Simple example of generating an embedding&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sentence_transformers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;model&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;mixedbread-ai/mxbai-embed-large-v1&amp;#39;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Hello world&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;model.encode&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;)  &lt;span style=&#34;color: #177500&#34;&gt;# This creates a numerical representation&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;Embedding shape: {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;embedding.shape&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;quot;&lt;/span&gt;)  &lt;span style=&#34;color: #177500&#34;&gt;# Typically (1024,) or similar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While traditional embeddings use floating-point numbers (typically 32-bit), binary quantized embeddings compress each value to just 1 bit, dramatically reducing storage requirements and improving search speed with minimal accuracy loss.&lt;/p&gt;
&lt;h2&gt;What is Quantization?&lt;/h2&gt;
&lt;p&gt;Quantization is the act of reducing high-precision float vectors into smaller number types, such as float16, float8, tiny, int, or bit-based formats so they take up less RAM and multiply faster. So by quantizating the float32 array of numbers, info a float16 array you half the size you need to store, and process. But also half the precision. And if you quantize down to a binary value for each float32, you reduce by 32x, going from 4KB per 1024 value (3 bytes per 1024 float) embedding, down to 0.13 KB (128 bytes) — all while our similarity search still returns roughly the same results.&lt;/p&gt;
&lt;h2&gt;What is SentenceTransformer?&lt;/h2&gt;
&lt;p&gt;SentenceTransformer is a Python framework that makes it easy to compute sentence and text embeddings. It&#39;s built on top of PyTorch and Transformers and gives us access to hundreds of pre-trained models.&lt;/p&gt;
&lt;p&gt;What makes SentenceTransformer particularly useful is how it simplifies the process of generating embeddings with just a few lines of code. The library handles all the complexity of transformer models while giving us easy-to-use interfaces.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;# Using SentenceTransformer to compare two sentences&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sentence_transformers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;util&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;model&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;mixedbread-ai/mxbai-embed-large-v1&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Generate embeddings for two sentences&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;embedding1&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;model.encode&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;I love programming&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;embedding2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;model.encode&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;I enjoy coding&amp;quot;&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Calculate similarity (cosine similarity ranges from -1 to 1)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;util.cos_sim&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;embedding1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;embedding2&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;Similarity: {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;similarity.item&lt;/span&gt;()&lt;span style=&#34;color: #C41A16&#34;&gt;:.4f}&amp;quot;&lt;/span&gt;)  &lt;span style=&#34;color: #177500&#34;&gt;# Should be high (close to 1)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;What is SQLite-Vec?&lt;/h2&gt;
&lt;p&gt;SQLite-Vec is a powerful SQLite extension that adds vector search capabilities to SQLite. It&#39;s particularly useful for our search system because it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Adds vector search operations directly into SQL&lt;/li&gt;
&lt;li&gt;Supports binary quantized embeddings out of the box&lt;/li&gt;
&lt;li&gt;Works with standard SQLite, which is already included in Python&lt;/li&gt;
&lt;li&gt;Provides efficient approximate nearest neighbor search&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SQLite-Vec is perfect for our use case because it combines the simplicity of SQLite with specialized vector operations that would otherwise require separate vector databases.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;# Basic example of SQLite-Vec setup&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite3&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite_vec&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;# Connect to SQLite and load the extension&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite3.connect&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;example.db&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;conn.enable_load_extension&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;sqlite_vec.load&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;conn.enable_load_extension&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;False&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Create a vector table&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;conn.execute&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    CREATE VIRTUAL TABLE IF NOT EXISTS vectors &lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    USING vec0(&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        id INTEGER PRIMARY KEY,&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        embedding bit[1024]  -- Binary vectors with 1024 dimensions&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    )&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Binary quantization: Why compress embeddings?&lt;/h2&gt;
&lt;p&gt;When working with embeddings, you&#39;ll quickly run into either storage, or more likely search speed issues. A standard float32 embedding with 1024 dimensions takes 4KB per document. For a million documents, that&#39;s 4GB just for embeddings. And you have to do multidimensional vector distance calculations for each of them when searching (though a lot of that can be cleverly optimised to cut down on how many you need to compute).&lt;/p&gt;
&lt;p&gt;Binary quantization compresses each number from a 32-bit float to a single bit (0 or 1), resulting in a 32x reduction in size. Here&#39;s how we can quantize embeddings:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sentence_transformers.quantization&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;quantize_embeddings&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;# Generate a standard embedding&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;model.encode&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Binary quantization makes embeddings tiny!&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;normalize_embeddings=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Quantize to binary (1 bit per dimension)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;binary_embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;quantize_embeddings&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;precision=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;ubinary&amp;quot;&lt;/span&gt;)

&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;Original size: {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;embedding.nbytes&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;} bytes&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;Binary size: {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;binary_embedding.nbytes&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;} bytes&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;Compression ratio: {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;embedding.nbytes&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;binary_embedding.nbytes&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}x&amp;quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While there&#39;s a small trade-off in accuracy, the benefits for most applications are enormous:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;32x smaller storage footprint&lt;/li&gt;
&lt;li&gt;Much faster similarity calculations using bit operations&lt;/li&gt;
&lt;li&gt;Better cache utilization&lt;/li&gt;
&lt;li&gt;Lower memory requirements&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And although lookup performance is worse than the full 32bit floats, you still get &lt;a href=&#34;https://emschwartz.me/binary-vector-embeddings-are-so-cool/&#34;&gt;96.45% accuracy&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Hamming distance: Faster binary embedding lookup speed&lt;/h2&gt;
&lt;p&gt;One of the most impressive advantages of binary embeddings isn&#39;t just their size—it&#39;s how much faster and efficient they are to compare. This is due to a fundamental shift in how we measure similarity.&lt;/p&gt;
&lt;p&gt;With traditional float32 embeddings, we typically use cosine similarity, which requires several expensive operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vector dot products (multiplication operations)&lt;/li&gt;
&lt;li&gt;Vector magnitude calculations (square roots)&lt;/li&gt;
&lt;li&gt;Division operations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In contrast, binary embeddings can use Hamming distance, which simply counts how many bits differ between two vectors. This can be implemented using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An XOR operation (extremely fast on modern CPUs)&lt;/li&gt;
&lt;li&gt;A population count (counting the 1s in the result)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These operations are dramatically faster than floating-point math:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;# Comparing binary embeddings with Hamming distance&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;numpy&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;np&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;# Create two binary vectors (already quantized)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;binary_vec1&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;np.array&lt;/span&gt;([&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;], &lt;span style=&#34;color: #000&#34;&gt;dtype=np.bool_&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;binary_vec2&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;np.array&lt;/span&gt;([&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;], &lt;span style=&#34;color: #000&#34;&gt;dtype=np.bool_&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Hamming distance calculation (XOR + bit count)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;hamming_distance&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;np.sum&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;binary_vec1&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;^&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;binary_vec2&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;Hamming distance: {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;hamming_distance&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;quot;&lt;/span&gt;)  &lt;span style=&#34;color: #177500&#34;&gt;# Number of differing bits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;SQLite-Vec takes full advantage of this property. When you store binary embeddings using &lt;code&gt;bit[1024]&lt;/code&gt; and the &lt;code&gt;vec_quantize_binary()&lt;/code&gt; function, SQLite-Vec automatically uses the much faster Hamming distance calculations during searches.&lt;/p&gt;
&lt;p&gt;According to benchmarks from &lt;a href=&#34;https://huggingface.co/blog/embedding-quantization#retrieval-speed&#34;&gt;MixedBread.ai and HuggingFace&lt;/a&gt;, this approach can yield a &lt;strong&gt;15-45x speedup in retrieval time (with a mean of 25x)&lt;/strong&gt; compared to traditional float32 embeddings, while retaining over 95% of the retrieval accuracy.&lt;/p&gt;
&lt;p&gt;That&#39;s why the combination of binary quantization and SQLite-Vec is so powerful for our RAG system—we get both the storage benefits and the computational speedup.&lt;/p&gt;
&lt;h2&gt;Setup and Installation&lt;/h2&gt;
&lt;p&gt;Let&#39;s get started by installing the required dependencies:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip install sentence-transformers
pip install sqlite-vec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We&#39;ll use the &lt;code&gt;mixedbread-ai/mxbai-embed-large-v1&lt;/code&gt; model, which generates high-quality text embeddings, and was designed to generate embeddings which support being quantized down to binary while retaining lookup accuracy.&lt;/p&gt;
&lt;p&gt;Quick test script to check that the dependencies are working.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;# Import Needed libraries&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sentence_transformers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite3&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite_vec&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;# Load model&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;model&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;mixedbread-ai/mxbai-embed-large-v1&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Test SQLite-Vec&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite3.connect&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;:memory:&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;conn.enable_load_extension&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;sqlite_vec.load&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;conn.enable_load_extension&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;False&lt;/span&gt;)

&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Setup complete! Everything is working correctly.&amp;quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Creating embeddings with mixedbread-ai/mxbai-embed-large-v1&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;mixedbread-ai/mxbai-embed-large-v1&lt;/code&gt; model generates embeddings and works perfectly with binary quantization.&lt;/p&gt;
&lt;p&gt;Example usage:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sentence_transformers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sentence_transformers.quantization&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;quantize_embeddings&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;# Load the model&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;model&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;mixedbread-ai/mxbai-embed-large-v1&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Generate embeddings for some text&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;This is an example sentence.&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;model.encode&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;normalize_embeddings=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Optionally quantize to binary&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;binary_embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;quantize_embeddings&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;precision=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;ubinary&amp;quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;normalize_embeddings=True&lt;/code&gt; parameter is important for binary quantization, as it ensures the values are properly distributed for optimal quantization.&lt;/p&gt;
&lt;p&gt;Now let&#39;s build a complete RAG system that uses SQLite-Vec for storage and binary quantized embeddings for efficient search:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;os&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite3&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite_vec&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sentence_transformers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;typing&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;List&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;Tuple&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;json&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;RAG&lt;/span&gt;:
    &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;Semantic text search for Retrieval Augmented Generation&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;db_path&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;):
        &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;Initialize the RAG system.&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Args:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            db_path: Path to the SQLite database file&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;.db_path&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;os.path.join&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;os.path.dirname&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;__file__&lt;/span&gt;), &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;rag.db&amp;quot;&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;db_path&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;db_path&lt;/span&gt;
        &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;._init_db&lt;/span&gt;()
        &lt;span style=&#34;color: #177500&#34;&gt;# Initialize the embedding model&lt;/span&gt;
        &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;.model&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;SentenceTransformer&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;mixedbread-ai/mxbai-embed-large-v1&amp;#39;&lt;/span&gt;)
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;_init_db&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;:
        &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;Initialize the SQLite database with required tables.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite3.connect&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;.db_path&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;conn.enable_load_extension&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;sqlite_vec.load&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;conn.enable_load_extension&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;False&lt;/span&gt;)
        
        &lt;span style=&#34;color: #177500&#34;&gt;# Create virtual table for vector storage with binary embeddings&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;conn.execute&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            CREATE VIRTUAL TABLE IF NOT EXISTS segments &lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            USING vec0(&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;                id TEXT PRIMARY KEY,       -- Segment ID&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;                text TEXT,                 -- Segment text&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;                embedding bit[1024],       -- Binary quantized embedding vector&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;                metadata TEXT              -- Optional metadata in JSON format&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            )&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;)
        
        &lt;span style=&#34;color: #000&#34;&gt;conn.commit&lt;/span&gt;()
        &lt;span style=&#34;color: #000&#34;&gt;conn.close&lt;/span&gt;()
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;_get_db_connection&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite3.Connection&lt;/span&gt;:
        &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;Get a database connection with sqlite-vec extension loaded.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sqlite3.connect&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;.db_path&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;conn.enable_load_extension&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;sqlite_vec.load&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt;)
        &lt;span style=&#34;color: #000&#34;&gt;conn.enable_load_extension&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;False&lt;/span&gt;)
        &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt;
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;load_segment&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;{}&amp;quot;&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;:
        &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;Load a segment into the database.&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Args:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            id: Unique identifier for the segment&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            text: The text content&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            metadata: Optional JSON metadata string&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;len&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text.strip&lt;/span&gt;()) &lt;span style=&#34;color: #000&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;:
            &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt;

        &lt;span style=&#34;color: #177500&#34;&gt;# Generate embedding using SentenceTransformer&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;.model.encode&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;normalize_embeddings=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)
        &lt;span style=&#34;color: #177500&#34;&gt;# Convert embedding to JSON string for SQLite-Vec&amp;#39;s vec_quantize_binary function&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;embedding_json&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;json.dumps&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;embedding.tolist&lt;/span&gt;())
        
        &lt;span style=&#34;color: #177500&#34;&gt;# Store in database using vec_quantize_binary&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;._get_db_connection&lt;/span&gt;()
        
        &lt;span style=&#34;color: #000&#34;&gt;conn.execute&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            INSERT OR REPLACE INTO segments (id, text, embedding, metadata)&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            VALUES (?, ?, vec_quantize_binary(?), ?)&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;, (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;embedding_json&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt;))
        
        &lt;span style=&#34;color: #000&#34;&gt;conn.commit&lt;/span&gt;()
        &lt;span style=&#34;color: #000&#34;&gt;conn.close&lt;/span&gt;()
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;search_segments&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;limit&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;Tuple&lt;/span&gt;[&lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt;]]:
        &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;Search for segments similar to the given text.&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Args:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            text: The text to search for&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            limit: Maximum number of results to return&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            &lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Returns:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            List of tuples containing (id, text, similarity_score, metadata)&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span style=&#34;color: #177500&#34;&gt;# Generate embedding for query text&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;query_embedding&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;.model.encode&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;normalize_embeddings=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;)
        &lt;span style=&#34;color: #177500&#34;&gt;# Convert query embedding to JSON string&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;query_embedding_json&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;query_embedding.tolist&lt;/span&gt;())
        
        &lt;span style=&#34;color: #177500&#34;&gt;# Search database using vec_quantize_binary for the query&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;._get_db_connection&lt;/span&gt;()
        
        &lt;span style=&#34;color: #000&#34;&gt;cursor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;conn.execute&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            SELECT id, text, distance, metadata &lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            FROM segments &lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            WHERE embedding MATCH vec_quantize_binary(?) AND k = ?&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            ORDER BY distance&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;, (&lt;span style=&#34;color: #000&#34;&gt;query_embedding_json&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;limit&lt;/span&gt;))
        
        &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; []
        &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;distance&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cursor.fetchall&lt;/span&gt;():
            &lt;span style=&#34;color: #177500&#34;&gt;# Convert distance to similarity score (normalize to [0,1] range)&lt;/span&gt;
            &lt;span style=&#34;color: #177500&#34;&gt;# The distance is a Hamming distance between binary vectors&lt;/span&gt;
            &lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;distance&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1024.0&lt;/span&gt;)  &lt;span style=&#34;color: #177500&#34;&gt;# 1024 is our vector dimension&lt;/span&gt;
            &lt;span style=&#34;color: #000&#34;&gt;results.append&lt;/span&gt;((&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt;))
        
        &lt;span style=&#34;color: #000&#34;&gt;conn.close&lt;/span&gt;()
        
        &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;remove_segment&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;:
        &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;Remove a segment from the database.&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Args:&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            id: The ID of the segment to remove&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;conn&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;._get_db_connection&lt;/span&gt;()
        &lt;span style=&#34;color: #000&#34;&gt;conn.execute&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;DELETE FROM segments WHERE id = ?&amp;quot;&lt;/span&gt;, (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;,))
        &lt;span style=&#34;color: #000&#34;&gt;conn.commit&lt;/span&gt;()
        &lt;span style=&#34;color: #000&#34;&gt;conn.close&lt;/span&gt;()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Using the RAG system&lt;/h2&gt;
&lt;p&gt;Here&#39;s how to use the RAG search:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;# Initialize the RAG system&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;rag&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;RAG&lt;/span&gt;()

&lt;span style=&#34;color: #177500&#34;&gt;# Add some documents&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;rag.load_segment&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;doc1&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Tokyo is the capital of Japan.&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;rag.load_segment&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;doc2&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Paris is the capital of France.&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;rag.load_segment&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;doc3&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Berlin is the capital of Germany.&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;rag.load_segment&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;doc4&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Rome is the capital of Italy.&amp;quot;&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Search for similar documents&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;rag.search_segments&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;What is the capital city of Japan?&amp;quot;&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Print results&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;:
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;{&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}: &amp;#39;{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;#39; (similarity: {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;similarity&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;:.4f})&amp;quot;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Why binary quantization?&lt;/h2&gt;
&lt;p&gt;Binary quantization offers several advantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Storage efficiency&lt;/strong&gt;: Reduces embedding size by 32x compared to float32&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search speed&lt;/strong&gt;: Binary operations are extremely fast on modern CPUs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory efficiency&lt;/strong&gt;: Allows storing millions of embeddings in memory&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simplicity&lt;/strong&gt;: Bit operations are supported by all database systems&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For most applications, the small accuracy trade-off is well worth these benefits. When working with large document collections, binary quantized embeddings make semantic search practical and cost-effective.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;We&#39;ve built a simple but powerful RAG system using binary quantized embeddings. This approach makes semantic search efficient and scalable even with limited resources.&lt;/p&gt;
&lt;h1&gt;References:&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://emschwartz.me/binary-vector-embeddings-are-so-cool/&#34;&gt;Binary vector embeddings are so cool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/blog/embedding-quantization#retrieval-speed&#34;&gt;Binary and Scalar Embedding Quantization for Significantly Faster &amp;amp; Cheaper Retrieval&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/blog/matryoshka&#34;&gt;Introduction to Matryoshka Embedding Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://huggingface.co/mixedbread-ai/mxbai-embed-large-v1&#34;&gt;mxbai-embed-large-v1 embedding model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/asg017/sqlite-vec&#34;&gt;sqlite-vec: Github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://alexgarcia.xyz/blog/2024/building-new-vector-search-sqlite/index.html&#34;&gt;I&#39;m writing a new vector search SQLite Extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://alexgarcia.xyz/blog/2024/sql-vector-search-languages/index.html&#34;&gt;Vector search in 7 different programming languages using SQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://alexgarcia.xyz/blog/2024/sqlite-vec-stable-release/index.html&#34;&gt;Introducing sqlite-vec v0.1.0: a vector search SQLite extension that runs everywhere&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://alexgarcia.xyz/blog/2024/sqlite-vec-hybrid-search/index.html&#34;&gt;Hybrid full-text search and vector search with SQLite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://alexgarcia.xyz/sqlite-vec/&#34;&gt;sqlite-vec: A vector search SQLite extension that runs anywhere!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.sbert.net&#34;&gt;SentenceTransformers Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/UKPLab/sentence-transformers&#34;&gt;Sentence Transformers: Embeddings, Retrieval, and Reranking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Example Code&lt;/h1&gt;
&lt;p&gt;I&#39;ve added the &lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/blob/master/Binary%20Quantized%20Embeddings%20Search/rag.py&#34;&gt;above example code&lt;/a&gt; to Github, with an extra command line interface to let you load in text and query the DB from the command line here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/tree/master/Binary%20Quantized%20Embeddings%20Search&#34;&gt;Github: ikyle.me-code-examples/Binary Quantized Embeddings Search&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Querying the iOS Photo Library</title>
    <id>https://ikyle.me/blog/2025/querying-the-ios-photo-library</id>
    <updated>2025-05-15T06:41:31.229506+00:00</updated>
    <published>2025-05-15T06:41:31.229506+00:00</published>
    <link href="https://ikyle.me/blog/2025/querying-the-ios-photo-library" />
    <summary type="text">Programatically access the iOS Photo Library with PhotoKit</summary>
    <content type="html">&lt;p&gt;To allow users to select a photo from their photo library the simplest option is to use &lt;a href=&#34;https://ikyle.me/blog/2020/uiimagepickercontroller&#34;&gt;UIImagePickerController&lt;/a&gt; to present the built in system interface to pick a photo and return it to your app.&lt;br /&gt;
However, &lt;code&gt;UIImagePickerController&lt;/code&gt; has limitations, such as no customisations, programmatic library access or even multi-selection. So if you need more access, or more control, than &lt;code&gt;UIImagePickerController&lt;/code&gt; provides you&#39;ll need to dig into the &lt;a href=&#34;https://developer.apple.com/documentation/photokit&#34;&gt;PhotoKit&lt;/a&gt; framework.&lt;/p&gt;
&lt;h1&gt;Required App Permissions&lt;/h1&gt;
&lt;p&gt;To access the users Photo library your app will need to ask for permission. First the app has to declare this in the &lt;code&gt;Info.plist&lt;/code&gt; file, using the &lt;code&gt;NSPhotoLibraryUsageDescription&lt;/code&gt; key and a description (which can be localised).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSPhotoLibraryUsageDescription&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;string&amp;gt;&lt;/span&gt;This app wants to use your photos.&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can query the current authorisation status using the API &lt;code&gt;[PHPhotoLibrary authorizationStatus]&lt;/code&gt; or &lt;code&gt;PHPhotoLibrary.authorizationStatus()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This API will return one of the following status codes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// The user has not yet made a choice&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;PHAuthorizationStatusNotDetermined&lt;/span&gt; &lt;span style=&#34;color: #177500&#34;&gt;// ObjC&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;PHAuthorizationStatus&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;notDetermined&lt;/span&gt; &lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Access is not authorized and the user cannot change this&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;// (possibly due to restrictions such as parental controls)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;PHAuthorizationStatusRestricted&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;PHAuthorizationStatus&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;restricted&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// The user has explicitly denied the app access&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;PHAuthorizationStatusDenied&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;PHAuthorizationStatus&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;denied&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// The user has authorized access to the photo library&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;PHAuthorizationStatusAuthorized&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;PHAuthorizationStatus&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;authorized&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The user will be prompted for permission, either when you explicitly ask for permission using the &lt;code&gt;requestAuthorization&lt;/code&gt; method on &lt;code&gt;PHPhotoLibrary&lt;/code&gt;, or the first time you call an authorisation required Photos framework method.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[&lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;requestAuthorization:^&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;PHAuthorizationStatus&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;status&lt;/span&gt;) {
   &lt;span style=&#34;color: #000&#34;&gt;NSLog&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;Permission Status: %ld&amp;quot;&lt;/span&gt;, (&lt;span style=&#34;color: #A90D91&#34;&gt;long&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;status&lt;/span&gt;);
}];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;requestAuthorization&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;handler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;status&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
   &lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Permission status: \(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;status&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;)&amp;quot;&lt;/span&gt;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Using the PhotoKit Framework&lt;/h1&gt;
&lt;h2&gt;Querying for Media&lt;/h2&gt;
&lt;p&gt;How to get the 50 most recently taken photos.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// 50 most recently created images&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchOptions&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sortDescriptors&lt;/span&gt; = [&lt;span style=&#34;color: #5B269A&#34;&gt;NSSortDescriptor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;key&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;creationDate&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;ascending&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;)]
&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchLimit&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;wantsIncrementalChangeDetails&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;result&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchResult&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;&amp;gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssets&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;with&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;)

&lt;span style=&#34;color: #000&#34;&gt;result&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;enumerateObjects&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: [], &lt;span style=&#34;color: #000&#34;&gt;using&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;stop&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;assets&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;How to get a list of 50 most recently taken screenshots.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchOptions&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sortDescriptors&lt;/span&gt; = [&lt;span style=&#34;color: #5B269A&#34;&gt;NSSortDescriptor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;key&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;creationDate&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;ascending&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;)]
&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchLimit&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;predicate&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;NSPredicate&lt;/span&gt;(
   &lt;span style=&#34;color: #000&#34;&gt;format&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;(mediaSubtype &amp;amp; %d) != 0&amp;quot;&lt;/span&gt;,
   &lt;span style=&#34;color: #000&#34;&gt;PHAssetMediaSubtype&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;photoScreenshot&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rawValue&lt;/span&gt;
)

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;result&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchResult&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;&amp;gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssets&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;with&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Documentation for the &lt;code&gt;predicate&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A predicate that specifies which properties to select results by and that also specifies any constraints on selection.&lt;br /&gt;
Construct a predicate with the properties of the class of objects that you want to fetch, listed in the PHFetchOptions table.&lt;br /&gt;
For example, the following code uses a predicate to fetch assets matching a specific set of mediaSubtypes values.&lt;br /&gt;
Photos does not support predicates created with the NSPredicate method init or the predicateWithBlock method.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;format&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt; = &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;(mediaSubtypes &amp;amp; %d) != 0 || (mediaSubtypes &amp;amp; %d) != 0&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fetchOptions&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchOptions&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;fetchOptions&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;predicate&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;NSPredicate&lt;/span&gt;(
   &lt;span style=&#34;color: #000&#34;&gt;format&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;format&lt;/span&gt;,
   &lt;span style=&#34;color: #000&#34;&gt;argumentArray&lt;/span&gt;: [&lt;span style=&#34;color: #000&#34;&gt;PHAssetMediaSubtype&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;photoPanorama&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;PHAssetMediaSubtype&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;videoHighFrameRate&lt;/span&gt;]
)

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fetchResult&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssets&lt;/span&gt;(
   &lt;span style=&#34;color: #000&#34;&gt;with&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;PHAssetMediaType&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;,
   &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;fetchOptions&lt;/span&gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;Class for Fetch Method&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;Supported Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;PHAsset&lt;/code&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;SELF&lt;/code&gt;, &lt;code&gt;localIdentifier&lt;/code&gt;, &lt;code&gt;creationDate&lt;/code&gt;, &lt;code&gt;modificationDate&lt;/code&gt;, &lt;code&gt;mediaType&lt;/code&gt;, &lt;code&gt;mediaSubtypes&lt;/code&gt;, &lt;code&gt;duration&lt;/code&gt;, &lt;code&gt;pixelWidth&lt;/code&gt;, &lt;code&gt;pixelHeight&lt;/code&gt;, &lt;code&gt;favorite&lt;/code&gt; (or &lt;code&gt;isFavorite&lt;/code&gt;), &lt;code&gt;hidden&lt;/code&gt; (or &lt;code&gt;isHidden&lt;/code&gt;), &lt;code&gt;burstIdentifier&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;PHAssetCollection&lt;/code&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;SELF&lt;/code&gt;, &lt;code&gt;localIdentifier&lt;/code&gt;, &lt;code&gt;localizedTitle&lt;/code&gt; (or &lt;code&gt;title&lt;/code&gt;), &lt;code&gt;startDate&lt;/code&gt;, &lt;code&gt;endDate&lt;/code&gt;, &lt;code&gt;estimatedAssetCount&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;PHCollectionList&lt;/code&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;SELF&lt;/code&gt;, &lt;code&gt;localIdentifier&lt;/code&gt;, &lt;code&gt;localizedTitle&lt;/code&gt; (or &lt;code&gt;title&lt;/code&gt;), &lt;code&gt;startDate&lt;/code&gt;, &lt;code&gt;endDate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;PHCollection&lt;/code&gt; (can fetch a mix of &lt;code&gt;PHCollectionList&lt;/code&gt; and &lt;code&gt;PHAssetCollection&lt;/code&gt; objects)&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;code&gt;SELF&lt;/code&gt;, &lt;code&gt;localIdentifier&lt;/code&gt;, &lt;code&gt;localizedTitle&lt;/code&gt; (or &lt;code&gt;title&lt;/code&gt;), &lt;code&gt;startDate&lt;/code&gt;, &lt;code&gt;endDate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;API Docs: &lt;a href=&#34;https://developer.apple.com/documentation/photos/phfetchoptions#overview&#34;&gt;https://developer.apple.com/documentation/photos/phfetchoptions#overview&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Querying Albums&lt;/h2&gt;
&lt;p&gt;How to fetch user-created albums and smart albums, then retrieve assets from a specific album.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Fetch user-created albums&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;userAlbums&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssetCollections&lt;/span&gt;(
    &lt;span style=&#34;color: #000&#34;&gt;with&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;album&lt;/span&gt;,
    &lt;span style=&#34;color: #000&#34;&gt;subtype&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;albumRegular&lt;/span&gt;,
    &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
)

&lt;span style=&#34;color: #177500&#34;&gt;// Fetch smart albums like Favorites, Recently Added, etc.&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;smartAlbums&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssetCollections&lt;/span&gt;(
    &lt;span style=&#34;color: #000&#34;&gt;with&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;smartAlbum&lt;/span&gt;,
    &lt;span style=&#34;color: #000&#34;&gt;subtype&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;any&lt;/span&gt;,
    &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
)

&lt;span style=&#34;color: #177500&#34;&gt;// Get assets from a specific album&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;getAssetsFromAlbum&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;album&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;limit&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Int&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;) -&amp;gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;] {
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchOptions&lt;/span&gt;()
    &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sortDescriptors&lt;/span&gt; = [&lt;span style=&#34;color: #5B269A&#34;&gt;NSSortDescriptor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;key&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;creationDate&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;ascending&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;)]
    &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;limit&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt; {
        &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchLimit&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;limit&lt;/span&gt;
    }
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;result&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssets&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;album&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;assets&lt;/span&gt; = [&lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;]()
    
    &lt;span style=&#34;color: #000&#34;&gt;result&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;enumerateObjects&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: [], &lt;span style=&#34;color: #000&#34;&gt;using&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;stop&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;assets&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;)
    })
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;assets&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Modifying Albums&lt;/h2&gt;
&lt;p&gt;How to create new albums, add assets to existing albums, and remove assets from albums.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Create a new album&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;createAlbum&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;withTitle&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;title&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;?) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;) {
	&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;placeholder&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;?
	
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shared&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;performChanges&lt;/span&gt;({
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;createRequest&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollectionChangeRequest&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollectionChangeRequest&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;creationRequestForAssetCollection&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;withTitle&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;title&lt;/span&gt;)
		&lt;span style=&#34;color: #000&#34;&gt;placeholder&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;createRequest&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;placeholderForCreatedAssetCollection&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;localIdentifier&lt;/span&gt;
		
	}, &lt;span style=&#34;color: #000&#34;&gt;completionHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;: (&lt;span style=&#34;color: #000&#34;&gt;any&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Error&lt;/span&gt;)?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;identifier&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;placeholder&lt;/span&gt;
		{
			&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fetchResult&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchResult&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;&amp;gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssetCollections&lt;/span&gt;(
				&lt;span style=&#34;color: #000&#34;&gt;withLocalIdentifiers&lt;/span&gt;: [&lt;span style=&#34;color: #000&#34;&gt;identifier&lt;/span&gt;],
				&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
			)
			
			&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;collection&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;? = &lt;span style=&#34;color: #000&#34;&gt;fetchResult&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;firstObject&lt;/span&gt;
			
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;collection&lt;/span&gt;)
			})
		}
		&lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; {
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
			})
		}
	})
}

&lt;span style=&#34;color: #177500&#34;&gt;// Add assets to an album&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;addAssets&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;assets&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;], &lt;span style=&#34;color: #000&#34;&gt;toAlbum&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;album&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;)
{
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shared&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;performChanges&lt;/span&gt;({
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;changeRequest&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollectionChangeRequest&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;album&lt;/span&gt;) {
			&lt;span style=&#34;color: #000&#34;&gt;changeRequest&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addAssets&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssets&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;withLocalIdentifiers&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;assets&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;map&lt;/span&gt; { &lt;span style=&#34;color: #000&#34;&gt;$0&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;localIdentifier&lt;/span&gt; }, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;NSFastEnumeration&lt;/span&gt;)
		}
		
	}, &lt;span style=&#34;color: #000&#34;&gt;completionHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;: (&lt;span style=&#34;color: #000&#34;&gt;any&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Error&lt;/span&gt;)?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
		&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
			&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;)
		})
	})
}

&lt;span style=&#34;color: #177500&#34;&gt;// Remove assets from an album&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;removeAssets&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;assets&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;], &lt;span style=&#34;color: #000&#34;&gt;fromAlbum&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;album&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;)
{
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shared&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;performChanges&lt;/span&gt;({
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;changeRequest&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollectionChangeRequest&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;album&lt;/span&gt;) {
			&lt;span style=&#34;color: #000&#34;&gt;changeRequest&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;removeAssets&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssets&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;withLocalIdentifiers&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;assets&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;map&lt;/span&gt; { &lt;span style=&#34;color: #000&#34;&gt;$0&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;localIdentifier&lt;/span&gt; }, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;NSFastEnumeration&lt;/span&gt;)
		}
		
	}, &lt;span style=&#34;color: #000&#34;&gt;completionHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;: (&lt;span style=&#34;color: #000&#34;&gt;any&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Error&lt;/span&gt;)?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
		&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
			&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;)
		})
	})
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Getting Folders of Albums&lt;/h2&gt;
&lt;p&gt;How to fetch folders containing albums and retrieve the albums within each folder.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Fetch folders that contain albums&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;getFolders&lt;/span&gt;() -&amp;gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt;] {
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fetchOptions&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchOptions&lt;/span&gt;()
    &lt;span style=&#34;color: #000&#34;&gt;fetchOptions&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sortDescriptors&lt;/span&gt; = [&lt;span style=&#34;color: #5B269A&#34;&gt;NSSortDescriptor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;key&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;localizedTitle&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;ascending&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;)]
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;foldersFetchResult&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchTopLevelUserCollections&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;with&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;fetchOptions&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;folders&lt;/span&gt; = [&lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt;]()
    
    &lt;span style=&#34;color: #000&#34;&gt;foldersFetchResult&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;enumerateObjects&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: [], &lt;span style=&#34;color: #000&#34;&gt;using&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;collection&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;stop&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;folder&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;collection&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt;? &lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt; {
            &lt;span style=&#34;color: #000&#34;&gt;folders&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;folder&lt;/span&gt;)
        }
    })
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;folders&lt;/span&gt;
}

&lt;span style=&#34;color: #177500&#34;&gt;// Get albums within a folder&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;getAlbumsInFolder&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;folder&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt;) -&amp;gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;] {
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fetchOptions&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchOptions&lt;/span&gt;()
    &lt;span style=&#34;color: #000&#34;&gt;fetchOptions&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sortDescriptors&lt;/span&gt; = [&lt;span style=&#34;color: #5B269A&#34;&gt;NSSortDescriptor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;key&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;localizedTitle&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;ascending&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;)]
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;albumsFetchResult&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHCollection&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchCollections&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;folder&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;fetchOptions&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;albums&lt;/span&gt; = [&lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;]()
    
    &lt;span style=&#34;color: #000&#34;&gt;albumsFetchResult&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;enumerateObjects&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: [], &lt;span style=&#34;color: #000&#34;&gt;using&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;collection&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;index&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;stop&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
        &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;album&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;collection&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt;? &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt; {
            &lt;span style=&#34;color: #000&#34;&gt;albums&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;album&lt;/span&gt;)
        }
    })
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;albums&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Modifying Folders&lt;/h2&gt;
&lt;p&gt;How to create new folders and add albums to existing folders in the photo library.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Create a new folder&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;createFolder&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;withTitle&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;title&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt;?) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;) {
	&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;placeholder&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;?
	
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shared&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;performChanges&lt;/span&gt;({
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;createRequest&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionListChangeRequest&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;creationRequestForCollectionList&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;withTitle&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;title&lt;/span&gt;)
		&lt;span style=&#34;color: #000&#34;&gt;placeholder&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;createRequest&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;placeholderForCreatedCollectionList&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;localIdentifier&lt;/span&gt;
		
	}, &lt;span style=&#34;color: #000&#34;&gt;completionHandler&lt;/span&gt;: { &lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;identifier&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;placeholder&lt;/span&gt;
		{
			&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fetchResult&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHFetchResult&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt;&amp;gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchCollectionLists&lt;/span&gt;(
				&lt;span style=&#34;color: #000&#34;&gt;withLocalIdentifiers&lt;/span&gt;: [&lt;span style=&#34;color: #000&#34;&gt;identifier&lt;/span&gt;],
				&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
			)
			
			&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;folder&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt;? = &lt;span style=&#34;color: #000&#34;&gt;fetchResult&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;firstObject&lt;/span&gt;
			
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;folder&lt;/span&gt;)
			})
		}
		&lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; {
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
			})
		}
	})
}

&lt;span style=&#34;color: #177500&#34;&gt;// Add albums to a folder&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;addAlbums&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;albums&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;], &lt;span style=&#34;color: #000&#34;&gt;toFolder&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;folder&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionList&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;) {
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shared&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;performChanges&lt;/span&gt;({
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;changeRequest&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHCollectionListChangeRequest&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;folder&lt;/span&gt;)
		{
			&lt;span style=&#34;color: #000&#34;&gt;changeRequest&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addChildCollections&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetCollection&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fetchAssetCollections&lt;/span&gt;(
				&lt;span style=&#34;color: #000&#34;&gt;withLocalIdentifiers&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;albums&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;map&lt;/span&gt;({ &lt;span style=&#34;color: #000&#34;&gt;$0&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;localIdentifier&lt;/span&gt; }),
				&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
			) &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;NSFastEnumeration&lt;/span&gt;)
		}
		
	}, &lt;span style=&#34;color: #000&#34;&gt;completionHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;: (&lt;span style=&#34;color: #000&#34;&gt;any&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Error&lt;/span&gt;)?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
		&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
			&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;)
		})
	})
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Getting thumbnails&lt;/h2&gt;
&lt;p&gt;How to fetch optimized thumbnail images for photo assets to display in a UI collection view.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Get thumbnail for UICollectionView&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;getThumbnail&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;targetSize&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGSize&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;?) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;)
{
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHImageRequestOptions&lt;/span&gt;()
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;deliveryMode&lt;/span&gt; = .&lt;span style=&#34;color: #000&#34;&gt;opportunistic&lt;/span&gt;
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;resizeMode&lt;/span&gt; = .&lt;span style=&#34;color: #000&#34;&gt;exact&lt;/span&gt;
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isNetworkAccessAllowed&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;
	
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHImageManager&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;default&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;requestImage&lt;/span&gt;(
		&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;targetSize&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;targetSize&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;contentMode&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;aspectFill&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;resultHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;?, &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;: [&lt;span style=&#34;color: #000&#34;&gt;AnyHashable&lt;/span&gt; : &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;)
			})
		})
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The options for the delivery mode are:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;/// Options for delivering requested image data&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;enum&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;PHImageRequestOptionsDeliveryMode&lt;/span&gt; {
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos automatically provides one or more results in order to balance image quality and responsiveness.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;opportunistic&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos provides only the highest-quality image available, regardless of how much time it takes to load.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;highQualityFormat&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos provides only a fast-loading image, possibly sacrificing image quality.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fastFormat&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There is also a property to control which version of the photo you would like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;version&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;PHImageRequestOptionsVersion&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;current&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;/// Options for requesting an image asset with or without adjustments.&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;enum&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;PHImageRequestOptionsVersion&lt;/span&gt; {
   &lt;span style=&#34;color: #177500&#34;&gt;/// Request the most recent version of the image asset (the one that reflects all edits).&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;current&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Request a version of the image asset without adjustments.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;unadjusted&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Request the original, highest-fidelity version of the image asset.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;original&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can also specify how you would like the image resized to the target size you asked for.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;resizeMode&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;PHImageRequestOptionsResizeMode&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;none&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;/// Options for how to resize the requested image to fit a target size.&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;enum&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;PHImageRequestOptionsResizeMode&lt;/span&gt; {
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos does not resize the image asset.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;none&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos efficiently resizes the image to a size similar to, or slightly larger than, the target size.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fast&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos resizes the image to match the target size exactly.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;exact&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can also specify how the image should be cropped.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;normalizedCropRect&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGRect&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;zero&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;To request a cropped image, specify the crop rectangle in a unit coordinate space relative to the image. In this coordinate system, the point {0.0,0.0} refers to the upper left corner of the image, and the point {1.0,1.0} refers to the opposite corner regardless of the image’s aspect ratio.&lt;br /&gt;
This property defaults to CGRectZero, which specifies no cropping.&lt;br /&gt;
If you specify a crop rectangle, you must also specify the PHImageRequestOptionsResizeMode.exact option for the resizeMode property.&lt;br /&gt;
&lt;a href=&#34;https://developer.apple.com/documentation/photos/phimagerequestoptions/normalizedcroprect&#34;&gt;https://developer.apple.com/documentation/photos/phimagerequestoptions/normalizedcroprect&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Getting Full Size Images&lt;/h2&gt;
&lt;p&gt;How to retrieve full-resolution images from the photo library for detailed viewing or editing.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Get full size image for editing&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;getFullSizeImage&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;?, [&lt;span style=&#34;color: #000&#34;&gt;AnyHashable&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]?) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;) {
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHImageRequestOptions&lt;/span&gt;()
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;deliveryMode&lt;/span&gt; = .&lt;span style=&#34;color: #000&#34;&gt;highQualityFormat&lt;/span&gt;
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isNetworkAccessAllowed&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isSynchronous&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;
	
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHImageManager&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;default&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;requestImage&lt;/span&gt;(
		&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;targetSize&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;PHImageManagerMaximumSize&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;contentMode&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;aspectFit&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;resultHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;?, &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;: [&lt;span style=&#34;color: #000&#34;&gt;AnyHashable&lt;/span&gt; : &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;)
			})
		}
	)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Editing Metadata&lt;/h2&gt;
&lt;p&gt;How to update photo metadata including location and favorite status.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Modify photo location&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;updateLocation&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;newLocation&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;CLLocation&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;)
{
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shared&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;performChanges&lt;/span&gt;({
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;request&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetChangeRequest&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;)
		&lt;span style=&#34;color: #000&#34;&gt;request&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;location&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;newLocation&lt;/span&gt;
		
	}, &lt;span style=&#34;color: #000&#34;&gt;completionHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;: (&lt;span style=&#34;color: #000&#34;&gt;any&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Error&lt;/span&gt;)?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
		&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
			&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;)
		})
	})
}

&lt;span style=&#34;color: #177500&#34;&gt;// Favorite or unfavorite an asset&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;setFavorite&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;isFavorite&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;)
{
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shared&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;performChanges&lt;/span&gt;({
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;request&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetChangeRequest&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;)
		&lt;span style=&#34;color: #000&#34;&gt;request&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isFavorite&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;isFavorite&lt;/span&gt;
		
	}, &lt;span style=&#34;color: #000&#34;&gt;completionHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;: (&lt;span style=&#34;color: #000&#34;&gt;any&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Error&lt;/span&gt;)?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
		&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
			&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;success&lt;/span&gt;)
		})
	})
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Getting Videos&lt;/h2&gt;
&lt;p&gt;How to fetch and stream video assets from the photo library, including iCloud videos.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Stream iCloud video&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;getVideoAsset&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #5B269A&#34;&gt;AVAsset&lt;/span&gt;?, [&lt;span style=&#34;color: #000&#34;&gt;AnyHashable&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]?) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;) {
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHVideoRequestOptions&lt;/span&gt;()
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;deliveryMode&lt;/span&gt; = .&lt;span style=&#34;color: #000&#34;&gt;automatic&lt;/span&gt;
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isNetworkAccessAllowed&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;
	
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHImageManager&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;default&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;requestAVAsset&lt;/span&gt;(
		&lt;span style=&#34;color: #000&#34;&gt;forVideo&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;resultHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;avAsset&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;AVAsset&lt;/span&gt;?, &lt;span style=&#34;color: #000&#34;&gt;audioMix&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;AVAudioMix&lt;/span&gt;?, &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;: [&lt;span style=&#34;color: #000&#34;&gt;AnyHashable&lt;/span&gt; : &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
			
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;avAsset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;)
			})
		}
	)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The options for the delivery mode are:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;enum&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;PHVideoRequestOptionsDeliveryMode&lt;/span&gt; {
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos automatically determines which quality of video data to provide based on the request and current conditions.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;automatic&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos provides only the highest quality video available.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;highQualityFormat&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos provides a video of moderate quality unless a higher quality version is locally cached.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;mediumQualityFormat&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Photos provides whatever quality of video can be most quickly loaded.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fastFormat&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There is also a property to control which version of the video you would like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;version&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;PHVideoRequestOptionsVersion&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;current&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;/// Options for requesting a video asset with or without adjustments.&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;enum&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;PHVideoRequestOptionsVersion&lt;/span&gt; {
   &lt;span style=&#34;color: #177500&#34;&gt;/// Request the most recent version of the video asset, reflecting all edits.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;current&lt;/span&gt;
   &lt;span style=&#34;color: #177500&#34;&gt;/// Request a version of the video asset without adjustments.&lt;/span&gt;
   &lt;span style=&#34;color: #A90D91&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;original&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Playing Live Photos&lt;/h2&gt;
&lt;p&gt;How to retrieve and display Live Photos with their interactive capabilities.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Get PHLivePhoto object for display&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;getLivePhoto&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;targetSize&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGSize&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #5B269A&#34;&gt;PHLivePhoto&lt;/span&gt;?, [&lt;span style=&#34;color: #000&#34;&gt;AnyHashable&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]?) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;) {
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHLivePhotoRequestOptions&lt;/span&gt;()
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;deliveryMode&lt;/span&gt; = .&lt;span style=&#34;color: #000&#34;&gt;highQualityFormat&lt;/span&gt;
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isNetworkAccessAllowed&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;
	
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHImageManager&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;default&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;requestLivePhoto&lt;/span&gt;(
		&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;targetSize&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;targetSize&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;contentMode&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;aspectFit&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;resultHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;livePhoto&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHLivePhoto&lt;/span&gt;?, &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;: [&lt;span style=&#34;color: #000&#34;&gt;AnyHashable&lt;/span&gt; : &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
			
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;livePhoto&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;)
			})
		}
	)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Get video from live photo&lt;/h2&gt;
&lt;p&gt;How to extract and access the video component from a Live Photo asset.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Extract video component from a Live Photo&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;getVideoFromLivePhoto&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAsset&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; (&lt;span style=&#34;color: #5B269A&#34;&gt;AVAsset&lt;/span&gt;?) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;)
{
	&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;mediaSubtypes&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;contains&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;photoLive&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; {
		&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt;
	}
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;resources&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetResource&lt;/span&gt;] = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetResource&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;assetResources&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;)
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;videoResource&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetResource&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;resources&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;first&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;where&lt;/span&gt;: { &lt;span style=&#34;color: #000&#34;&gt;$0&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;type&lt;/span&gt; == .&lt;span style=&#34;color: #000&#34;&gt;pairedVideo&lt;/span&gt; }) &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; {
		&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt;
	}
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetResourceRequestOptions&lt;/span&gt;()
	&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isNetworkAccessAllowed&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fileURL&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;URL&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;FileManager&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;default&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;temporaryDirectory&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;appendingPathComponent&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;\(&lt;/span&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;NSUUID&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;()&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;uuidString&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;).mov&amp;quot;&lt;/span&gt;)
	
	&lt;span style=&#34;color: #5B269A&#34;&gt;PHAssetResourceManager&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;default&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;requestData&lt;/span&gt;(
		&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;videoResource&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;dataReceivedHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;data&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;Data&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
			&lt;span style=&#34;color: #177500&#34;&gt;// This handler might be called multiple times&lt;/span&gt;
			&lt;span style=&#34;color: #A90D91&#34;&gt;try&lt;/span&gt;? &lt;span style=&#34;color: #000&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;to&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;fileURL&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;atomic&lt;/span&gt;)
		},
		&lt;span style=&#34;color: #000&#34;&gt;completionHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;: (&lt;span style=&#34;color: #000&#34;&gt;any&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Error&lt;/span&gt;)?) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
			&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt; == &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt; {
				&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;AVAsset&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;fileURL&lt;/span&gt;)
				&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
					&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;)
				})
			}
			&lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; {
				&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
					&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
				})
			}
		}
	)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Getting updates to your PHFetchResult&lt;/h2&gt;
&lt;p&gt;How to track and respond to changes in the photo library using the persistent change API.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;/// Using the new change history API to track changes&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;PhotoLibraryChangeTracker&lt;/span&gt; {
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;lastChangeToken&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;PHPersistentChangeToken&lt;/span&gt;?
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;init&lt;/span&gt;() {
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;lastChangeToken&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shared&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;currentChangeToken&lt;/span&gt;
	}
	
	&lt;span style=&#34;color: #177500&#34;&gt;/// Fetch all asset changes since the last change update `lastChangeToken`&lt;/span&gt;
	&lt;span style=&#34;color: #177500&#34;&gt;/// - Parameter completion: inserted, updated, deleted&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fetchChanges&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: @&lt;span style=&#34;color: #000&#34;&gt;escaping&lt;/span&gt; ([&lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;], [&lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;], [&lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;]) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Void&lt;/span&gt;) {
		&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;token&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;PHPersistentChangeToken&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;lastChangeToken&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; {
			&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;([], [], [])
			&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt;
		}
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;do&lt;/span&gt; {
			&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;persistentChanges&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;PHPersistentChangeFetchResult&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;try&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;PHPhotoLibrary&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shared&lt;/span&gt;().&lt;span style=&#34;color: #000&#34;&gt;fetchPersistentChanges&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;since&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;token&lt;/span&gt;)
			
			&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;insertedIdentifiers&lt;/span&gt; = [&lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;]()
			&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;updatedIdentifiers&lt;/span&gt; = [&lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;]()
			&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;deletedIdentifiers&lt;/span&gt; = [&lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;]()
			
			&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;persistentChange&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;persistentChanges&lt;/span&gt; {
				&lt;span style=&#34;color: #A90D91&#34;&gt;do&lt;/span&gt; {
					&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;changeDetails&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;PHPersistentObjectChangeDetails&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;try&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;persistentChange&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;changeDetails&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;asset&lt;/span&gt;)
					
					&lt;span style=&#34;color: #000&#34;&gt;insertedIdentifiers&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;contentsOf&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;changeDetails&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;insertedLocalIdentifiers&lt;/span&gt;)
					&lt;span style=&#34;color: #000&#34;&gt;updatedIdentifiers&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;contentsOf&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;changeDetails&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;updatedLocalIdentifiers&lt;/span&gt;)
					&lt;span style=&#34;color: #000&#34;&gt;deletedIdentifiers&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;contentsOf&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;changeDetails&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;deletedLocalIdentifiers&lt;/span&gt;)
					
					&lt;span style=&#34;color: #177500&#34;&gt;// Store the last change token for future use&lt;/span&gt;
					&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;lastChangeToken&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;persistentChange&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;changeToken&lt;/span&gt;
				}
				&lt;span style=&#34;color: #A90D91&#34;&gt;catch&lt;/span&gt; {
					&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;ERROR: \(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;)&amp;quot;&lt;/span&gt;)
				}
			}
			
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;insertedIdentifiers&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;updatedIdentifiers&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;deletedIdentifiers&lt;/span&gt;)
			})
		}
		&lt;span style=&#34;color: #A90D91&#34;&gt;catch&lt;/span&gt;
			&lt;span style=&#34;color: #000&#34;&gt;PHPhotosError&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;persistentChangeTokenExpired&lt;/span&gt;,
			&lt;span style=&#34;color: #000&#34;&gt;PHPhotosError&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;persistentChangeDetailsUnavailable&lt;/span&gt;
		{
			&lt;span style=&#34;color: #177500&#34;&gt;// Handle errors by refetching tracked objects&lt;/span&gt;
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;([], [], [])
			})
		}
		&lt;span style=&#34;color: #A90D91&#34;&gt;catch&lt;/span&gt; {
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;([], [], [])
			})
		}
	}
	
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Show iOS System Dictionary Screen</title>
    <id>https://ikyle.me/blog/2025/show-ios-system-dictionary</id>
    <updated>2025-05-06T01:42:32.435253+00:00</updated>
    <published>2025-05-06T01:42:32.435253+00:00</published>
    <link href="https://ikyle.me/blog/2025/show-ios-system-dictionary" />
    <summary type="text">How to show the iOS system dictionary word lookup screen programmatically </summary>
    <content type="html">&lt;h1&gt;Show iOS System Dictionary Screen&lt;/h1&gt;
&lt;p&gt;iOS includes a built-in dictionary that can be shown to the user programmatically using &lt;code&gt;UIReferenceLibraryViewController&lt;/code&gt;. This allows you to show word definitions directly in your app.&lt;/p&gt;
&lt;h2&gt;Using UIReferenceLibraryViewController&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;UIReferenceLibraryViewController&lt;/code&gt; is simple to use, but also very limited.&lt;br /&gt;
Initialize it with a term to look up, then present the &lt;code&gt;UIViewController&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;UIKit&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Check if dictionary is available for the word&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;term&lt;/span&gt; = &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;serendipity&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dictionaryAvailable&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIReferenceLibraryViewController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dictionaryHasDefinition&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;forTerm&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;term&lt;/span&gt;)

&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dictionaryAvailable&lt;/span&gt; {
    &lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Has definition in one of the currently downloaded dictionaries&amp;quot;&lt;/span&gt;)
}
&lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; {
    &lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Does not have a definition in one of the currently downloaded dictionaries&amp;quot;&lt;/span&gt;)
}

&lt;span style=&#34;color: #177500&#34;&gt;// Create and present the dictionary view controller&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dictionaryViewController&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIReferenceLibraryViewController&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;term&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;term&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;present&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;dictionaryViewController&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;animated&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Even if there is no definition available, presenting the &lt;code&gt;UIReferenceLibraryViewController&lt;/code&gt; anyway is a good idea.&lt;br /&gt;
If the user has no dictionaries downloaded, there will be no definition found for any word. However, the presented &lt;code&gt;UIReferenceLibraryViewController&lt;/code&gt; will show the user an option to download a dictionary file.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;All Dictionaries&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;Definition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/show-ios-system-dictionary/simulator_screenshot_definition.png&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2025/show-ios-system-dictionary/simulator_screenshot_dictionary.png&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Swift Map vs CompactMap vs FlatMap</title>
    <id>https://ikyle.me/blog/2024/swift-map-flatmap-compactmap</id>
    <updated>2024-10-17T01:57:52.894801+00:00</updated>
    <published>2024-10-17T01:57:52.894801+00:00</published>
    <link href="https://ikyle.me/blog/2024/swift-map-flatmap-compactmap" />
    <summary type="text">Understanding the differences</summary>
    <content type="html">&lt;p&gt;It can be confusing to remember the difference, so this is a cheatsheet for reference.&lt;/p&gt;
&lt;h1&gt;Swift &lt;code&gt;.map()&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://developer.apple.com/documentation/swift/sequence/map(_:)&#34;&gt;&lt;code&gt;map({})&lt;/code&gt;&lt;/a&gt; transforms all elements in a list.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;list&lt;/span&gt; = [&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;about&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;testing&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;100&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;300&amp;quot;&lt;/span&gt;]
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;list&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// [&amp;quot;about&amp;quot;, &amp;quot;testing&amp;quot;, &amp;quot;1&amp;quot;, &amp;quot;100&amp;quot;, &amp;quot;300&amp;quot;]&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;mappedList&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;list&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;map&lt;/span&gt;({ &lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;$0&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;reversed&lt;/span&gt;()) })
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;mappedList&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// [&amp;quot;tuoba&amp;quot;, &amp;quot;gnitset&amp;quot;, &amp;quot;1&amp;quot;, &amp;quot;001&amp;quot;, &amp;quot;003&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;.compactMap()&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://developer.apple.com/documentation/swift/array/compactmap(_:)&#34;&gt;&lt;code&gt;compactMap()&lt;/code&gt;&lt;/a&gt; compacts the list, by removing the nil items. (&lt;code&gt;filterMap&lt;/code&gt; was the original name for the function &lt;a href=&#34;https://github.com/swiftlang/swift-evolution/blob/main/proposals/0187-introduce-filtermap.md&#34;&gt;when it was proposed&lt;/a&gt;)&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;list&lt;/span&gt; = [&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;about&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;testing&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;100&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;300&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;]
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;list&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// [Optional(&amp;quot;about&amp;quot;), nil, Optional(&amp;quot;testing&amp;quot;), Optional(&amp;quot;1&amp;quot;), nil, Optional(&amp;quot;100&amp;quot;), Optional(&amp;quot;300&amp;quot;), nil]&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;compactedList&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;list&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;compactMap&lt;/span&gt;({ &lt;span style=&#34;color: #000&#34;&gt;$0&lt;/span&gt; })
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;compactedList&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// [&amp;quot;about&amp;quot;, &amp;quot;testing&amp;quot;, &amp;quot;1&amp;quot;, &amp;quot;100&amp;quot;, &amp;quot;300&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;.flatMap()&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://developer.apple.com/documentation/swift/array/flatmap(_:)-i3mr&#34;&gt;&lt;code&gt;flatMap()&lt;/code&gt;&lt;/a&gt; flattens a list of other lists by combining their contents.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;list&lt;/span&gt; = [ [&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;1&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;2&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;3&amp;quot;&lt;/span&gt;], [&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;b&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;c&amp;quot;&lt;/span&gt;], [&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;kyle&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;harry&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;tom&amp;quot;&lt;/span&gt;] ]
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;list&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// [[&amp;quot;1&amp;quot;, &amp;quot;2&amp;quot;, &amp;quot;3&amp;quot;], [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;], [&amp;quot;kyle&amp;quot;, &amp;quot;harry&amp;quot;, &amp;quot;tom&amp;quot;]]&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;compactedList&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;list&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;flatMap&lt;/span&gt;({ &lt;span style=&#34;color: #000&#34;&gt;$0&lt;/span&gt; })
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;compactedList&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// [&amp;quot;1&amp;quot;, &amp;quot;2&amp;quot;, &amp;quot;3&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, &amp;quot;kyle&amp;quot;, &amp;quot;harry&amp;quot;, &amp;quot;tom&amp;quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Running Local LLMs on macOS</title>
    <id>https://ikyle.me/blog/2024/run-local-llms-on-macos</id>
    <updated>2024-08-08T03:46:31.472765+00:00</updated>
    <published>2024-08-08T03:46:31.472765+00:00</published>
    <link href="https://ikyle.me/blog/2024/run-local-llms-on-macos" />
    <summary type="text">How to install and run ChatGPT style LLM models locally on macOS</summary>
    <content type="html">&lt;p&gt;To install and run ChatGPT style LLM models locally and offline on macOS the easiest way is with either &lt;a href=&#34;https://github.com/ggerganov/llama.cpp&#34;&gt;llama.cpp&lt;/a&gt; or &lt;a href=&#34;https://ollama.com/download&#34;&gt;Ollama&lt;/a&gt; (which basically just wraps &lt;a href=&#34;https://github.com/ggerganov/llama.cpp&#34;&gt;llama.cpp&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/ggerganov/llama.cpp&#34;&gt;llama.cpp&lt;/a&gt; is one of those open source libraries which is what actually powers most more user facing applications. (like ffmpeg if you do anything with audio or video)&lt;/p&gt;
&lt;p&gt;It loads and runs LLM model files and allows you to use them via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An &lt;a href=&#34;https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#web-server&#34;&gt;OpenAI API compatible web server&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Directly from Terminal &lt;a href=&#34;https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#interactive-mode&#34;&gt;via the cli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A variety of &lt;a href=&#34;https://github.com/ggerganov/llama.cpp?tab=readme-ov-file#description&#34;&gt;supported UIs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It has a lot of advanced features and supports all sorts of options.&lt;/p&gt;
&lt;p&gt;However, you still have to get the LLM models and make sure they are the correct format, and use the right chat template (which if they are in llama.cpp&#39;s file format should be included in the model file).&lt;/p&gt;
&lt;h1&gt;Ollama&lt;/h1&gt;
&lt;p&gt;When Ollama runs a model, it basically just starts the &lt;code&gt;llama.cpp&lt;/code&gt; CLI with some default parameters.&lt;br /&gt;
However, Ollama doesn&#39;t just run models.&lt;/p&gt;
&lt;p&gt;It also includes &lt;a href=&#34;https://ollama.com/library&#34;&gt;a library of pre-quantised&lt;/a&gt; (shrunken to load faster and fit in less vram) models available for easy download.&lt;br /&gt;
And a homrbrew style CLI interface for downloading the models.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;Download both the GUI tool from &lt;a href=&#34;https://ollama.com/download&#34;&gt;https://ollama.com/download&lt;/a&gt; and install &lt;a href=&#34;https://formulae.brew.sh/formula/ollama&#34;&gt;the cli tool from homebrew&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew install ollama
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Usage&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Find a model you want to run.&lt;br /&gt;
The best current (2024-08) small models are:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ollama.com/library/llama3.1:8b&#34;&gt;Llama3.1 8b&lt;/a&gt; (meta)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ollama.com/library/mistral-nemo:12b&#34;&gt;Mistral NeMo 12b&lt;/a&gt; (mistral)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ollama.com/library/gemma2:9b&#34;&gt;Gemma 2 9b&lt;/a&gt; (Google)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Llama3.1 is considered the best currently available open source model, but the 8b version struggles with tool usage, and the 70b is too big for a lot of computers.&lt;br /&gt;
Mistral NeMo 12b is slightly bigger, but handles tool usage much better.&lt;br /&gt;
Gemma 2 doesn&#39;t support tool usage but is a very capable general model.&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;Download the model&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ollama pull mistral-nemo:12b
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Run the Model&lt;/h2&gt;
&lt;p&gt;To run the llm directly from the command line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ollama run mistral-nemo:12b
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You&#39;ll now have an interactive cli prompt you can use to talk to the LLM.&lt;br /&gt;
Like a terminal version of ChatGPT.&lt;/p&gt;
&lt;p&gt;However, you&#39;ll notice the LLMs have mostly been trained to answer with markdown, so &lt;a href=&#34;https://github.com/ollama/ollama/tree/main?tab=readme-ov-file#web--desktop&#34;&gt;a UI is most useful&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Start the web server&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ollama serve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Interact via web API&lt;/h3&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl http://localhost:11434/api/chat -d &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;{&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;  &amp;quot;model&amp;quot;: &amp;quot;llama3.1&amp;quot;,&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;  &amp;quot;messages&amp;quot;: [&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    { &amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: &amp;quot;why is the sky blue?&amp;quot; }&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;  ]&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As long as you have the asked for model downloaded it&#39;ll load the model and run the command.&lt;/p&gt;
&lt;h2&gt;Useful Notes&lt;/h2&gt;
&lt;p&gt;As the web API can load models on demand it can be useful to see what models are currently loaded in ram (by default kept for 4 minutes after the last API call, this &lt;a href=&#34;https://github.com/ollama/ollama/blob/main/docs/faq.md#how-do-i-keep-a-model-loaded-in-memory-or-make-it-unload-immediately&#34;&gt;can be changed&lt;/a&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ollama ps
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;NAME&lt;/span&gt;      	&lt;span style=&#34;color: #000&#34;&gt;ID&lt;/span&gt;          	&lt;span style=&#34;color: #000&#34;&gt;SIZE&lt;/span&gt; 	&lt;span style=&#34;color: #000&#34;&gt;PROCESSOR&lt;/span&gt;	&lt;span style=&#34;color: #A90D91&#34;&gt;UNTIL&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;llama3&lt;/span&gt;:&lt;span style=&#34;color: #1C01CE&#34;&gt;70&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;b&lt;/span&gt;	&lt;span style=&#34;color: #000&#34;&gt;bcfb190ca3a7&lt;/span&gt;	&lt;span style=&#34;color: #1C01CE&#34;&gt;42&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;GB&lt;/span&gt;	&lt;span style=&#34;color: #1C01CE&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;GPU&lt;/span&gt; 	&lt;span style=&#34;color: #1C01CE&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;minutes&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;List models downloaded&lt;/h3&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ollama list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;NAME                              	ID          	SIZE  	MODIFIED     
mistral-nemo:latest               	4b300b8c6a97	7.1 GB	7 days ago  	
llama3.1:8b                       	62757c860e01	4.7 GB	7 days ago  	
phi3:medium-128k                  	3aeb385c7040	7.9 GB	4 weeks ago 	
gemma2:latest                     	ff02c3702f32	5.4 GB	4 weeks ago 	
llama3:8b                         	365c0bd3c000	4.7 GB	4 weeks ago 	
phi3:mini-128k                    	d184c916657e	2.2 GB	4 weeks ago 	
phi3:3.8-mini-128k-instruct-q5_K_M	5a696b4e6899	2.8 GB	2 months ago	
phi3:latest                       	a2c89ceaed85	2.3 GB	2 months ago
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Ram Limitations&lt;/h3&gt;
&lt;p&gt;To run an LLM model it needs to fit in your vram, the GPUs ram. On macOS the M series chips having unified memory shared between the CPU and GPU is great for this!
On PC systems you are limited to the 16 GB of vRam on the RTX 4080, or 24GB of vram on the RTX 3090 and RTX 4090.&lt;/p&gt;
&lt;p&gt;However, Macs can have as much vram as they have normal ram*.
The actual limit is the system only gives about 2/3 to the GPU at max, and needs some for the rest of the system, so you don&#39;t actually get to use all of it.
You can tweak the limit: &lt;code&gt;sudo sysctl iogpu.wired_limit_mb=&amp;lt;mb&amp;gt;&lt;/code&gt; (&lt;a href=&#34;https://github.com/ggerganov/llama.cpp/discussions/2182#discussioncomment-7698315&#34;&gt;source&lt;/a&gt;), but you need to be careful doing so.&lt;/p&gt;
&lt;p&gt;All this means when picking a model to download and run, you can&#39;t just pick the biggest, and best, model.&lt;br /&gt;
You have to pick one that will fit inside your vram.&lt;/p&gt;
&lt;p&gt;Models take up roughly their download size + a little extra (for the prompt and processing).&lt;/p&gt;
&lt;p&gt;So Llama 3.1 8b takes up 16GB for the &lt;a href=&#34;https://ollama.com/library/llama3.1:8b-instruct-fp16&#34;&gt;base 16 bit floating point model&lt;/a&gt;.&lt;br /&gt;
However, if the model is quantised down to storing all those model weights as &lt;a href=&#34;https://ollama.com/library/llama3.1:8b-instruct-q4_K_S&#34;&gt;4 bit ints instead&lt;/a&gt;, it is now 4.7 GB.&lt;br /&gt;
The model gets a bit dumber doing this, but not by that much most of the time.&lt;/p&gt;
&lt;p&gt;If you pick a model which is tool big, then llama.cpp will actually stop trying to load it into the GPU and start using CPU ram instead, doing some of the processing on the CPU and some on the GPU.&lt;br /&gt;
This is MUCH slower and should be avoided if you can.&lt;br /&gt;
Also as M chip mac&#39;s share the memory between the 2 anyway, this isn&#39;t as effective and you might still get a crash if trying to load a model that is too big.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Links&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/ollama/ollama/tree/main?tab=readme-ov-file#cli-reference&#34;&gt;CLI Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/ollama/ollama/tree/main/docs&#34;&gt;Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ollama.com/library&#34;&gt;Most Popular Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ollama.com/library?sort=newest&#34;&gt;All Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.reddit.com/r/LocalLLaMA/&#34;&gt;r/LocalLLaMA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Pretty Print JSON in the Terminal</title>
    <id>https://ikyle.me/blog/2024/pretty-print-json-terminal</id>
    <updated>2024-08-08T02:32:53.507775+00:00</updated>
    <published>2024-08-08T02:32:53.507762+00:00</published>
    <link href="https://ikyle.me/blog/2024/pretty-print-json-terminal" />
    <summary type="text">How to pretty print JSON on the command line</summary>
    <content type="html">&lt;p&gt;I was testing out a web API quickly with curl and it printed minified JSON to the terminal.&lt;br /&gt;
Instead of trying to read that densely packed json text, this is how to pretty print the json on the terminal instead.&lt;/p&gt;
&lt;p&gt;The tool &lt;a href=&#34;https://github.com/jqlang/jq&#34;&gt;jq&lt;/a&gt; is designed to handle and format JSON data on the command line.&lt;/p&gt;
&lt;p&gt;Here’s how you can do it:&lt;/p&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;p&gt;Install it using &lt;a href=&#34;https://brew.sh&#34;&gt;homebrew&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew install jq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;Use the following command to pretty-print the JSON response from &lt;code&gt;curl&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl -s &amp;lt;URL&amp;gt; | jq .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-s&lt;/code&gt;: This option makes &lt;code&gt;curl&lt;/code&gt; operate in silent mode (no progress meter or error messages).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;URL&amp;gt;&lt;/code&gt;: Replace this with the URL you are making the request to.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;| jq .&lt;/code&gt;: This pipes the output of &lt;code&gt;curl&lt;/code&gt; to &lt;code&gt;jq&lt;/code&gt;, which formats the JSON. (the &lt;code&gt;.&lt;/code&gt; is an argument)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Example&lt;/h4&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl -s https://api.example.com/data | jq .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This will print the JSON response in a readable, pretty-printed format.&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/jqlang/jq&#34;&gt;jq&lt;/a&gt; is actually meant not to pretty print json, but to transform and manipulate json data.&lt;br /&gt;
The &lt;code&gt;.&lt;/code&gt; argument to it tells it to return the input unchanged as the output.
This implies (correctly) other arguments change the input json before outputting it.&lt;/p&gt;
&lt;p&gt;A fully tutorial on it&#39;s abilities is available on it&#39;s website: &lt;a href=&#34;https://jqlang.github.io/jq/tutorial/&#34;&gt;jqlang.github.io/jq/tutorial/&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">How to Turn On an LG C2 with Home Assistant</title>
    <id>https://ikyle.me/blog/2023/how-to-turn-on-lg-c2-with-home-assistant</id>
    <updated>2023-02-09T03:54:05.107912+00:00</updated>
    <published>2023-02-09T03:54:05.107912+00:00</published>
    <link href="https://ikyle.me/blog/2023/how-to-turn-on-lg-c2-with-home-assistant" />
    <summary type="text">Setting up WakeOnLAN and configuring input sources for an LG C2 in Home Assistant</summary>
    <content type="html">&lt;p&gt;&lt;a href=&#34;https://www.home-assistant.io&#34;&gt;HomeAssistant&lt;/a&gt; automatically detects and supports LG webOS smart TVs with the &lt;a href=&#34;https://www.home-assistant.io/integrations/webostv&#34;&gt;LG webOS Smart TV integration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2023/how-to-turn-on-lg-c2-with-home-assistant/1-auto-detect.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h1&gt;Turn On TV&lt;/h1&gt;
&lt;p&gt;However, by default you can turn the TV off, but not back on again.&lt;br /&gt;
In order to turn the TV on you need to manually setup an action on the device which calls the &lt;a href=&#34;https://www.home-assistant.io/integrations/webostv#turn-on-action&#34;&gt;WakeOnLan service&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As far as I understand, the reason for this is because the &lt;code&gt;WakeOnLan&lt;/code&gt; service is a separate integration and integrations can&#39;t install/call each other automatically.&lt;/p&gt;
&lt;h1&gt;Setup&lt;/h1&gt;
&lt;p&gt;1: Enable the &lt;a href=&#34;https://www.home-assistant.io/integrations/wake_on_lan/&#34;&gt;WakeOnLan&lt;/a&gt; integration.&lt;br /&gt;
(requires editing the &lt;code&gt;configuration.yaml&lt;/code&gt; manually).&lt;/p&gt;
&lt;p&gt;2: Inside the device details for the TV.&lt;br /&gt;
Create a new automation triggered by &lt;code&gt;Device is requested to turn on&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2023/how-to-turn-on-lg-c2-with-home-assistant/2-automation.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;3: Add a &lt;code&gt;Call service&lt;/code&gt; action.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2023/how-to-turn-on-lg-c2-with-home-assistant/3-call-service.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;4: Select call &lt;code&gt;Wake on LAN&lt;/code&gt; send magic packet&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2023/how-to-turn-on-lg-c2-with-home-assistant/4-wake-on-lan.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;5: Add the MAC address of the TV.&lt;/p&gt;
&lt;p&gt;The instructions on the Home Assistant documentation doesn&#39;t mention this, but for me to get this to work I also needed to fill in the &lt;code&gt;Broadcast address&lt;/code&gt; and set it to the local subnet&#39;s broadcast IP, and unselect the &lt;code&gt;Broadcast port&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As my IP address range is &lt;code&gt;192.168.50.1-254&lt;/code&gt; this is &lt;code&gt;192.168.50.255&lt;/code&gt; for me.&lt;br /&gt;
For you it will likely be either &lt;code&gt;192.168.0.255&lt;/code&gt; or &lt;code&gt;192.168.1.255&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2023/how-to-turn-on-lg-c2-with-home-assistant/5-wake-on-lan-setup.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;This automation should be automatically picked up by the webOS integration and means you will now have fully power ON and OFF controls for the TV device.&lt;/p&gt;
&lt;h1&gt;Finished Setup&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2023/how-to-turn-on-lg-c2-with-home-assistant/6-lg-c2-home-assistant-control.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Creating a CoreImage Filter with a Metal Shader</title>
    <id>https://ikyle.me/blog/2022/creating-a-coreimage-filter-with-a-metal-kernel</id>
    <updated>2022-07-03T00:56:34.030222+00:00</updated>
    <published>2022-07-03T00:56:34.030222+00:00</published>
    <link href="https://ikyle.me/blog/2022/creating-a-coreimage-filter-with-a-metal-kernel" />
    <summary type="text">How to setup Xcode to build a Metal Shader for use with a custom CoreImage Kernel</summary>
    <content type="html">&lt;p&gt;In my recent article explaining how to make a &lt;a href=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing&#34;&gt;metaball style iOS Shape Morphing effect&lt;/a&gt; I glossed over how to use a Metal CoreImage filter; instead of using the deprecated Core Image Kernel Language Shader.&lt;/p&gt;
&lt;p&gt;The reason for glossing over it because it is annoyingly complicated!&lt;/p&gt;
&lt;p&gt;Where as showing you how to use the Core Image Kernel Language Shader based shader as as simple as this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// LumaThresholdFilter.swift&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;LumaThresholdFilter&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;CIFilter&lt;/span&gt;
{
	&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;threshold&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;thresholdKernel&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;CIColorKernel&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt;:&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;kernel vec4 thresholdFilter(__sample image, float threshold)&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;{&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;	float luma = (image.r * 0.2126) + (image.g * 0.7152) + (image.b * 0.0722);&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;	return (luma &amp;gt; threshold) ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 0.0);&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;}&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;
	
	&lt;span style=&#34;color: #177500&#34;&gt;//&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;@objc&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;dynamic&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; : &lt;span style=&#34;color: #5B269A&#34;&gt;CIImage&lt;/span&gt;?
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;outputImage&lt;/span&gt; : &lt;span style=&#34;color: #5B269A&#34;&gt;CIImage&lt;/span&gt;!
	{
		&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt;
		{
			&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
		}
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;arguments&lt;/span&gt; = [&lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;threshold&lt;/span&gt;)] &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; [&lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;thresholdKernel&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;apply&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;extent&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;extent&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;arguments&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;arguments&lt;/span&gt;)
	}
	
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To explain how to use the Metal shader version requires quite a few more steps.&lt;/p&gt;
&lt;h1&gt;Custom Core Image Filters with Metal Shaders&lt;/h1&gt;
&lt;p&gt;Unlike with Core Image Kernel Language shaders you can&#39;t just include the shader as a string. Instead them most be in separate &lt;code&gt;.metal&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;This has positives: like compile time error checking; and negatives: no tricks involving dynamic manipulation/generation of shaders.&lt;/p&gt;
&lt;p&gt;So to start with we&#39;ll move our &lt;code&gt;thresholdFilter&lt;/code&gt; shader function from the string into its own file &lt;code&gt;LumaThreshold.metal&lt;/code&gt;.&lt;br /&gt;
Except no. We&#39;ll actually name it &lt;code&gt;LumaThreshold.ci.metal&lt;/code&gt; not because we have to, but because it&#39;ll help us later.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// LumaThreshold.ci.metal&lt;/span&gt;

&lt;span style=&#34;color: #633820&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color: #177500&#34;&gt;&amp;lt;metal_stdlib&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;metal&lt;/span&gt;;
&lt;span style=&#34;color: #633820&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color: #177500&#34;&gt;&amp;lt;CoreImage/CoreImage.h&amp;gt;&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;extern&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;lumaThreshold&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;coreimage::sample_t&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pixelColor&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;threshold&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;coreimage::destination&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;destination&lt;/span&gt;)
{
	&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pixelRGB&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pixelColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rgb&lt;/span&gt;;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;luma&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;pixelRGB&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.2126&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;pixelRGB&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;g&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.7152&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;pixelRGB&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0722&lt;/span&gt;);
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;luma&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;threshold&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now to load this in our CoreImage filter subclass we&#39;ll load it like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;thresholdKernel&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;CIColorKernel&lt;/span&gt; = { () -&amp;gt; &lt;span style=&#34;color: #5B269A&#34;&gt;CIColorKernel&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;Bundle&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;forResource&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;LumaThreshold&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;withExtension&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;ci.metallib&amp;quot;&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;data&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;try&lt;/span&gt;! &lt;span style=&#34;color: #000&#34;&gt;Data&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;contentsOf&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt;)
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;do&lt;/span&gt; {
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;try&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;CIColorKernel&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;functionName&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;lumaThreshold&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;fromMetalLibraryData&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;data&lt;/span&gt;)
	}
	&lt;span style=&#34;color: #A90D91&#34;&gt;catch&lt;/span&gt; {
		&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;\(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;)&amp;quot;&lt;/span&gt;)
		&lt;span style=&#34;color: #5B269A&#34;&gt;fatalError&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;\(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;)&amp;quot;&lt;/span&gt;)
	}
}()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you were observant you might notice we made a &lt;code&gt;LumaThreshold.ci.metal&lt;/code&gt; file in the source code, but are now loading a &lt;code&gt;LumaThreshold.ci.metallib&lt;/code&gt; file at runtime.&lt;/p&gt;
&lt;p&gt;When we try to build and run the app now we&#39;ll get a crash with the error message:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; 
&lt;span style=&#34;color: #000&#34;&gt;^&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;MetaballEffectFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;swift&lt;/span&gt;:&lt;span style=&#34;color: #1C01CE&#34;&gt;56&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;Fatal&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;Unexpectedly&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;found&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;unwrapping&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;an&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Optional&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That&#39;s because first we need to preprocess the Metal files; unfortunately, Xcode doesn&#39;t do this automatically for you.&lt;/p&gt;
&lt;p&gt;The WWDC 2020 video &lt;a href=&#34;https://developer.apple.com/videos/play/wwdc2020/10021&#34;&gt;Build Metal-based Core Image kernels with Xcode&lt;/a&gt; shows you how to set up the needed custom build steps.&lt;br /&gt;
However, if yo follow it exactly you will get more runtime errors, as the instructions are slightly wrong.&lt;/p&gt;
&lt;p&gt;As of June 2022 using Xcode 13.4.1 these are the needed steps:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;# First convert LumaThreshold.ci.metal to LumaThreshold.ci.air&lt;/span&gt;
xcrun metal -c &lt;span style=&#34;color: #000&#34;&gt;$MTL_HEADER_SEARCH_PATHS&lt;/span&gt; -fcikernel  &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;LumaThreshold.ci.metal&amp;quot;&lt;/span&gt; -o &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;LumaThreshold.ci.air&amp;quot;&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;# Then convert LumaThreshold.ci.air to LumaThreshold.ci.metallib&lt;/span&gt;
xcrun metallib -cikernel &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;LumaThreshold.ci.air&amp;quot;&lt;/span&gt; -o &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;LumaThreshold.ci.metallib&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To do this automatically we&#39;ll setup some custom build rules like suggested in the WWDC video.&lt;/p&gt;
&lt;p&gt;In the Xcode Project Settings &amp;gt; Build Rules tab, click on the + icon to setup a new build rule.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/creating-a-coreimage-filter-with-a-metal-kernel/build-rules.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Then create 2 new rules:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;First the one for &lt;code&gt;*.ci.metal&lt;/code&gt; files.&lt;br /&gt;
This is why we named it &lt;code&gt;.ci.metal&lt;/code&gt;, instead of just &lt;code&gt;.metal&lt;/code&gt;. So we can process this separately from any non CoreImage related Metal files we might have.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Source files with names matching: *.ci.metal
[ ] Run once per architecture
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Custom script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;xcrun metal -c &lt;span style=&#34;color: #000&#34;&gt;$MTL_HEADER_SEARCH_PATHS&lt;/span&gt; -fcikernel  &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;${&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;INPUT_FILE_PATH&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;quot;&lt;/span&gt; -o &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;${&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;SCRIPT_OUTPUT_FILE_0&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Output Files&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;$(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;DERIVED_FILE_DIR&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;$(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;INPUT_FILE_BASE&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;.air&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;p&gt;Next step a step to turn these files into new &lt;code&gt;.air&lt;/code&gt; files into &lt;code&gt;*.metallib&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Source files with names matching: *.ci.air
[ ] Run once per architecture
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Custom script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;xcrun metallib -cikernel &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;${&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;INPUT_FILE_PATH&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;quot;&lt;/span&gt; -o &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;${&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;SCRIPT_OUTPUT_FILE_0&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Output Files&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;$(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;METAL_LIBRARY_OUTPUT_DIR&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;$(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;INPUT_FILE_BASE&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;.metallib&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/creating-a-coreimage-filter-with-a-metal-kernel/custom-build-rules.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: The first above command is slightly different from the one in the WWWDC video.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The WWDC video from 2020 shows this command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;xcrun metal -c -I &lt;span style=&#34;color: #000&#34;&gt;$MTL_HEADER_SEARCH_PATHS&lt;/span&gt; -fcikernel  &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;${&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;INPUT_FILE_PATH&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;quot;&lt;/span&gt; -o &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;${&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;SCRIPT_OUTPUT_FILE_0&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you use that however, it will strip out the function during linking and when you run the project you will get this error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;MetaballEffectFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;swift&lt;/span&gt;:&lt;span style=&#34;color: #1C01CE&#34;&gt;64&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;Fatal&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;Error&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Domain&lt;/span&gt;=&lt;span style=&#34;color: #000&#34;&gt;CIKernel&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Code&lt;/span&gt;=&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;(null)&amp;quot;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;UserInfo&lt;/span&gt;={&lt;span style=&#34;color: #000&#34;&gt;CINonLocalizedDescriptionKey&lt;/span&gt;=&lt;span style=&#34;color: #000&#34;&gt;Function&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;does&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;exist&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;library&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;data&lt;/span&gt;. 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The problem is the &lt;code&gt;-I&lt;/code&gt; remove that and your build scripts setup should work fine.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If we build and run now, we should have a fully working CoreImage filter using a Metal Shader.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/creating-a-coreimage-filter-with-a-metal-kernel/metaballs-close-small.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h1&gt;Useful Links&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://vijaysharma.ca/creating-a-coreimage-filter-using-metal-kernels/&#34;&gt;Creating a CoreImage Filter using Metal Kernels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/documentation/coreimage/writing_custom_kernels&#34;&gt;Apple: Writing Custom Kernels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pspdfkit.com/blog/2020/image-filters-using-metal/&#34;&gt;PSPDFKIT: Using Metal to Apply Image Filters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/videos/play/wwdc2021/10159/&#34;&gt;WWDC 2021: Explore Core Image kernel improvements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/videos/play/wwdc2020/10021/&#34;&gt;WWDC 2020: Build Metal-based Core Image kernels with Xcode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf&#34;&gt;Apple: Metal Shading Language Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Creating Cool UI: iOS Shape Morphing</title>
    <id>https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing</id>
    <updated>2022-07-02T02:15:01.083349+00:00</updated>
    <published>2022-07-02T02:15:01.083349+00:00</published>
    <link href="https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing" />
    <summary type="text">Recreating cool UI demos I&#39;ve seen online. This time, morphing between different icon shapes and learning about metaballs.</summary>
    <content type="html">&lt;p&gt;The other day on Twitter I came across &lt;a href=&#34;https://twitter.com/DLX/status/1541101687267594252&#34;&gt;a tweet showing a really cool UI demo&lt;/a&gt; and wondered how it was done.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/DLX/status/1541101687267594252&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing/twitter-example-video-480p.mp4&#34; alt=&#34;&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Seeing this tweet reminded me of an idea I&#39;ve had for several years to do a blog post series showing how to actually create various cool UI concept videos, Dribbble posts, or weird UI elements from other apps.&lt;/p&gt;
&lt;p&gt;So, as hopefully the first of many, let&#39;s see how to a Create Cool UI Component: &#34;Shape Morphing&#34; on iOS.&lt;/p&gt;
&lt;h2&gt;Metaballs&lt;/h2&gt;
&lt;p&gt;The technique for organic looking objects morphing between and into each other is known as &lt;a href=&#34;https://en.wikipedia.org/wiki/Metaballs&#34;&gt;metaballs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As two objects get close together, instead of waiting until they start to overlap.
&lt;img src=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing/balls-close-small.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The two objects will instead start stretching towards each other and combining.
&lt;img src=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing/metaballs-close-small.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h1&gt;iOS Implementation&lt;/h1&gt;
&lt;p&gt;To implement this effect we need to combine 2 different effects together (at least the way we are going to go about it, there are a few other ways to achieve this effect; which are linked at the bottom of the page).&lt;/p&gt;
&lt;p&gt;We need to render our shapes and then apply these effects over the top as post processing steps to the rendered image.&lt;/p&gt;
&lt;p&gt;CALayer&#39;s do have a &lt;a href=&#34;https://developer.apple.com/documentation/quartzcore/calayer/1410901-filters&#34;&gt;&lt;code&gt;.filters&lt;/code&gt; property&lt;/a&gt; which looks perfect for this!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An array of Core Image filters to apply to the contents of the layer and its sublayers. Animatable&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Until you get to the bottom of the documentation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This property is not supported on layers in iOS&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;🥲&lt;/p&gt;
&lt;p&gt;Sadly such rendering effects aren&#39;t supported on iOS.&lt;br /&gt;
So instead we&#39;ll need to use a rendering API which does let us post-process the rendered result.&lt;/p&gt;
&lt;p&gt;On iOS we have the choice of either SpriteKit or SceneKit.&lt;/p&gt;
&lt;p&gt;SceneKit has a cool trick of letting you set a UIView as the material of a node, which would be really helpful in applying the trick to UIView&#39;s as we could host the views inside the SceneKit view.&lt;/p&gt;
&lt;p&gt;However, for now I&#39;ll just use SpriteKit as I don&#39;t need anything more for this example and it is simpler to work with.&lt;/p&gt;
&lt;h1&gt;SpriteKit Implementation&lt;/h1&gt;
&lt;p&gt;First we need to setup our scene:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;SimpleViewController&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIViewController&lt;/span&gt; {
	
	&lt;span style=&#34;color: #177500&#34;&gt;// Our SpriteKit Objects&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;skView&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;SKView&lt;/span&gt;()
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;SKScene&lt;/span&gt;()
	
	&lt;span style=&#34;color: #177500&#34;&gt;// The two balls&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blobOne&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;SKShapeNode&lt;/span&gt;? = &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blobTwo&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;SKShapeNode&lt;/span&gt;? = &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewDidLoad&lt;/span&gt;() {
		&lt;span style=&#34;color: #A90D91&#34;&gt;super&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;viewDidLoad&lt;/span&gt;()
		
		&lt;span style=&#34;color: #177500&#34;&gt;// Setup Scene&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scaleMode&lt;/span&gt; = .&lt;span style=&#34;color: #000&#34;&gt;resizeFill&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;physicsWorld&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;gravity&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;CGVector&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;dx&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;dy&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;skView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;presentScene&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;)
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addSubview&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;skView&lt;/span&gt;);
		
		&lt;span style=&#34;color: #177500&#34;&gt;// Create the 2 balls&lt;/span&gt;
		{
			&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;SKShapeNode&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;circleOfRadius&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;)
			&lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fillColor&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;yellow&lt;/span&gt;
			&lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;strokeColor&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fillColor&lt;/span&gt;
			
			&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addChild&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;)
			&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;blobOne&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;
		}();
		
		{
			&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;SKShapeNode&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;circleOfRadius&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;)
			&lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fillColor&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;white&lt;/span&gt;
			&lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;strokeColor&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fillColor&lt;/span&gt;
			
			&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addChild&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;)
			&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;blobTwo&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;blob&lt;/span&gt;
		}();
	}
	
	&lt;span style=&#34;color: #177500&#34;&gt;// Set the positions of everything&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewDidLayoutSubviews&lt;/span&gt;() {
		&lt;span style=&#34;color: #A90D91&#34;&gt;super&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;viewDidLayoutSubviews&lt;/span&gt;()
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;skView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;center&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGPoint&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;midX&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;midY&lt;/span&gt;)
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;blobOne&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;position&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;center&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;applying&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;CGAffineTransform&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;translationX&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;))
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;blobTwo&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;position&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;center&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;applying&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;CGAffineTransform&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;translationX&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;))
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This gives us our basic scene. Two spheres near each other but only just touching.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing/two-metaballs-1-basic-small.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Now let&#39;s add a blur effect at the end of &lt;code&gt;-viewDidLoad()&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// - At the top of the file....&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;CoreImage&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;CoreImage&lt;/span&gt;.&lt;span style=&#34;color: #3F6E75&#34;&gt;CIFilterBuiltins&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// - At the bottom of viewDidLoad&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Create &lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blur&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;CIFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;gaussianBlur&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;blur&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;radius&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;20&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;filter&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;blur&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Make sure the scene uses the filter we created&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shouldEnableEffects&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing/two-metaballs-2-blur-small.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Now that we&#39;ve done that we can see how the shapes are merged together as their blurs overlap and merge.&lt;/p&gt;
&lt;p&gt;However, this still doesn&#39;t look like the effect we want. To achieve that we will need to apply a threshold filter where we make fully opaque everything over a certain threshold, and we hide every pixel below that.&lt;br /&gt;
This will make sure our shapes still have precise boundaries and appear to merge, not just blur together.&lt;/p&gt;
&lt;p&gt;We can&#39;t apply multiple filters to the SpriteKit scene, so we&#39;ll need to subclass &lt;code&gt;CIFilter&lt;/code&gt; and make our own combined filter.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;MetaballEffectFilter&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;CIFilter&lt;/span&gt;
{
	&lt;span style=&#34;color: #177500&#34;&gt;// Internal Filters&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blurFilter&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;CIFilter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;CIGaussianBlur&lt;/span&gt; = {
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blur&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;CIFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;gaussianBlur&lt;/span&gt;()
		&lt;span style=&#34;color: #000&#34;&gt;blur&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;radius&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;30&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blur&lt;/span&gt;
	}()
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;thresholdFilter&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;LumaThresholdFilter&lt;/span&gt;()
	
	
	&lt;span style=&#34;color: #177500&#34;&gt;// - CIFilter Subclass Properties&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;@objc&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;dynamic&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; : &lt;span style=&#34;color: #5B269A&#34;&gt;CIImage&lt;/span&gt;?
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;outputImage&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;CIImage&lt;/span&gt;!
	{
		&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt;
		{
			&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
		}
		
		&lt;span style=&#34;color: #177500&#34;&gt;// Blur Image&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;blurFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blurredOutput&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;blurFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;outputImage&lt;/span&gt;
		
		&lt;span style=&#34;color: #177500&#34;&gt;// Clip to the threshold set&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;thresholdFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;blurredOutput&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;thresholdFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;outputImage&lt;/span&gt;
	}
	
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;First we blur the image, then clip the output to the threshold.&lt;br /&gt;
But where does this &lt;code&gt;LumaThresholdFilter&lt;/code&gt; come from? This is another class we&#39;ll need to make ourselves.&lt;/p&gt;
&lt;p&gt;The easier way to do this is to use &lt;code&gt;Core Image Kernel Language API&lt;/code&gt; which is deprecated. Instead you are meant to use the new metal shader CoreImage filter API.&lt;br /&gt;
However, to make CoreImage metal shaders requires several more steps of tweaking your build config by adding custom build rules and build settings. So for now I&#39;ll show examples of both the metal and CoreImage shaders but will use the &lt;code&gt;Core Image Kernel Language&lt;/code&gt; for the rest of the examples afterwards.&lt;/p&gt;
&lt;p&gt;Metal Shader&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// LumaThreshold.ci.metal&lt;/span&gt;

&lt;span style=&#34;color: #633820&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color: #177500&#34;&gt;&amp;lt;metal_stdlib&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;namespace&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;metal&lt;/span&gt;;
&lt;span style=&#34;color: #633820&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color: #177500&#34;&gt;&amp;lt;CoreImage/CoreImage.h&amp;gt;&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;extern&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;C&amp;quot;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;lumaThreshold&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;coreimage::sample_t&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pixelColor&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;threshold&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;coreimage::destination&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;destination&lt;/span&gt;)
{
	&lt;span style=&#34;color: #000&#34;&gt;float3&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pixelRGB&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pixelColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rgb&lt;/span&gt;;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;float&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;luma&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;pixelRGB&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.2126&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;pixelRGB&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;g&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.7152&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;pixelRGB&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;b&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0722&lt;/span&gt;);
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;luma&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;threshold&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;float4&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Core Image Kernel Language Shader&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// LumaThresholdFilter.swift&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;LumaThresholdFilter&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;CIFilter&lt;/span&gt;
{
	&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;threshold&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;thresholdKernel&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;CIColorKernel&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt;:&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;kernel vec4 thresholdFilter(__sample image, float threshold)&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;{&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;	float luma = (image.r * 0.2126) + (image.g * 0.7152) + (image.b * 0.0722);&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;	return (luma &amp;gt; threshold) ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 0.0);&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;}&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;
	
	&lt;span style=&#34;color: #177500&#34;&gt;//&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;@objc&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;dynamic&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; : &lt;span style=&#34;color: #5B269A&#34;&gt;CIImage&lt;/span&gt;?
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;outputImage&lt;/span&gt; : &lt;span style=&#34;color: #5B269A&#34;&gt;CIImage&lt;/span&gt;!
	{
		&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt;
		{
			&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
		}
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;arguments&lt;/span&gt; = [&lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;Float&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;threshold&lt;/span&gt;)] &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; [&lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;thresholdKernel&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;apply&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;extent&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;inputImage&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;extent&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;arguments&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;arguments&lt;/span&gt;)
	}
	
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This now gives us our final effect:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing/two-metaballs-3-clamp-small.png&#34; alt=&#34;&#34; /&gt;
&lt;img src=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing/metaball-bounce-loop-short.mp4&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h1&gt;From Metaballs to Icon Morphing&lt;/h1&gt;
&lt;p&gt;Now we have our metaballs effect. But we still don&#39;t have the same icon morphing as shown in the original Twitter video. However, we now have everything we need.&lt;/p&gt;
&lt;p&gt;Now we just need to fade in the new icon over the top of the old icon, while fading the old icon out.&lt;/p&gt;
&lt;p&gt;First we need to swap the 2 circles for 1 icon. We&#39;ll also need to keep hold of the filter reference this time, so we can animate it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;currentIcon&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;SKSpriteNode&lt;/span&gt;?

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;filter&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;MetaballEffectFilter&lt;/span&gt;()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And let&#39;s add some button on screen for us to use.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Property&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;lazy&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;buttons&lt;/span&gt; : [&lt;span style=&#34;color: #5B269A&#34;&gt;UIButton&lt;/span&gt;] = { [&lt;span style=&#34;color: #A90D91&#34;&gt;unowned&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;] &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; [
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;circle.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;heart.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;star.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;bell.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;bookmark.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;tag.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;bolt.fill&amp;quot;&lt;/span&gt;,
		
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;play.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;pause.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;squareshape.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;key.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;hexagon.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;gearshape.fill&amp;quot;&lt;/span&gt;,
		&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;car.fill&amp;quot;&lt;/span&gt;,
	].&lt;span style=&#34;color: #5B269A&#34;&gt;map&lt;/span&gt;({ &lt;span style=&#34;color: #000&#34;&gt;buttonName&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIButton&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;Configuration&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;filled&lt;/span&gt;()
		&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;systemName&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;buttonName&lt;/span&gt;)
		&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;baseBackgroundColor&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;white&lt;/span&gt;
		&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;baseForegroundColor&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;black&lt;/span&gt;
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;button&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIButton&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;configuration&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;)
		
		&lt;span style=&#34;color: #000&#34;&gt;button&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addAction&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UIAction&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;handler&lt;/span&gt;: { [&lt;span style=&#34;color: #A90D91&#34;&gt;weak&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;] &lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
			&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;animateIconChange&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;newIconName&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;buttonName&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;)
		}), &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;touchUpInside&lt;/span&gt;)
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;button&lt;/span&gt;
	})
}()


&lt;span style=&#34;color: #177500&#34;&gt;// ...in viewDidLoad()&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;buttons&lt;/span&gt; {
	&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addSubview&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;button&lt;/span&gt;)
}


&lt;span style=&#34;color: #177500&#34;&gt;// .. In viewDidLayoutSubviews()&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;buttonSize&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGSize&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGSize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;70&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;)

&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; (&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;safeAreaInsets&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bottom&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;5&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;buttonSize&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;)

&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;button&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;buttons&lt;/span&gt; {
	&lt;span style=&#34;color: #000&#34;&gt;button&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGRect&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;origin&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;zero&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;size&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;buttonSize&lt;/span&gt;)
	&lt;span style=&#34;color: #000&#34;&gt;button&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;center&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;buttonSize&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;)
	&lt;span style=&#34;color: #000&#34;&gt;button&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;center&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;buttonSize&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;)
	
	&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;buttonSize&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;5&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;size&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;buttonSize&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;5&lt;/span&gt;) {
		&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;
		&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;buttonSize&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;5&lt;/span&gt;
	}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now let&#39;s setup showing the icon. (Uses &lt;a href=&#34;https://gist.github.com/kylehowells/27e6e1bf42f283d22c3ae8bb9faaeece&#34;&gt;this UIImage helper&lt;/a&gt;).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// ...at the end of viewDidLoad()&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;animateIconChange&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;newIconName&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;circle.fill&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)


&lt;span style=&#34;color: #177500&#34;&gt;// Create new method&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;animateIconChange&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;newIconName&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;showPlayIcon&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;false&lt;/span&gt;) {
	&lt;span style=&#34;color: #177500&#34;&gt;// Create new icon shape&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;newIconShape&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;SKSpriteNode&lt;/span&gt;? = {
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;iconSize&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGSize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;80&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;80&lt;/span&gt;)
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;systemName&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;newIconName&lt;/span&gt;)?.&lt;span style=&#34;color: #000&#34;&gt;withTintColor&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;white&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;resized&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;within&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;iconSize&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt; }
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;SKTexture&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;)
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sprite&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;SKSpriteNode&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;texture&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;size&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;iconSize&lt;/span&gt;)
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sprite&lt;/span&gt;
	}()
	
	&lt;span style=&#34;color: #000&#34;&gt;newIconShape&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;position&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGPoint&lt;/span&gt;(
		&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;midX&lt;/span&gt;,
		&lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;midY&lt;/span&gt;
	)
	&lt;span style=&#34;color: #000&#34;&gt;newIconShape&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;alpha&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;
	
	&lt;span style=&#34;color: #177500&#34;&gt;// Add new icon&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;newIconShape&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;newIconShape&lt;/span&gt; {
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addChild&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;newIconShape&lt;/span&gt;)
	}
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;oldIconShape&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;currentIcon&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;currentIcon&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;currentIcon&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;newIconShape&lt;/span&gt;
	
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt; == &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt; {
		&lt;span style=&#34;color: #000&#34;&gt;newIconShape&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;alpha&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;
		&lt;span style=&#34;color: #000&#34;&gt;oldIconShape&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;removeFromParent&lt;/span&gt;()
		&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt;
	}

	...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now we have our basic structure.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing/icon-morph-demo-small.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;To animate the change similarly to how it appears on Twitter we want to do several things in quick succession:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First animate the blur from 0 to something.&lt;/li&gt;
&lt;li&gt;Halfway through that, start alpha fading in the new shape and out the old shape.&lt;/li&gt;
&lt;li&gt;About 3/4rd way through the shapes fading from one to the other, then animate the blur back down to 0 so by the end you end up left with just the new target shape itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;	&lt;span style=&#34;color: #177500&#34;&gt;// ... continues&lt;/span&gt;
	
	&lt;span style=&#34;color: #177500&#34;&gt;// Animate the change&lt;/span&gt;
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fadeDuration&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.25&lt;/span&gt;)
	
	&lt;span style=&#34;color: #177500&#34;&gt;// Animate in the blur effect&lt;/span&gt;
	&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;animateBlur&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;fadeDuration&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blur&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;5&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
	
	&lt;span style=&#34;color: #177500&#34;&gt;// Wait then start fading in the new icon and out the old&lt;/span&gt;
	&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;asyncAfter&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;deadline&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;now&lt;/span&gt;() &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;fadeDuration&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.75&lt;/span&gt;), &lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;swapDuration&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;
		
		&lt;span style=&#34;color: #000&#34;&gt;newIconShape&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;SKAction&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fadeAlpha&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;to&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;swapDuration&lt;/span&gt;))
		&lt;span style=&#34;color: #000&#34;&gt;oldIconShape&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;SKAction&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fadeAlpha&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;to&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;swapDuration&lt;/span&gt;))
		
		&lt;span style=&#34;color: #177500&#34;&gt;// Wait, then start returning the view back to a non-blobby version&lt;/span&gt;
		&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;asyncAfter&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;deadline&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;now&lt;/span&gt;() &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;swapDuration&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.75&lt;/span&gt;), &lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
			
			&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;animateBlur&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;fadeDuration&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blur&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;5&lt;/span&gt;)
			
			&lt;span style=&#34;color: #177500&#34;&gt;// Cleanup&lt;/span&gt;
			&lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;asyncAfter&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;deadline&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;now&lt;/span&gt;() &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fadeDuration&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;execute&lt;/span&gt;: {
				&lt;span style=&#34;color: #000&#34;&gt;oldIconShape&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;removeFromParent&lt;/span&gt;()
			})
		})
	})
}

&lt;span style=&#34;color: #177500&#34;&gt;// Helper which animates the blur&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;animateBlur&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blur&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;targetBlur&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt;) {
	&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blurFade&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;SKAction&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;customAction&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;withDuration&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;actionBlock&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;node&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;elapsed&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;percent&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;elapsed&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;duration&lt;/span&gt;)
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;difference&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;targetBlur&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt;)
		&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;currentBlur&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;difference&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;percent&lt;/span&gt;)
		
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;filter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;blurFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setValue&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;currentBlur&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;forKey&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;kCIInputRadiusKey&lt;/span&gt;)
		&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shouldEnableEffects&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;
	})
	
	&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;run&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;blurFade&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As the different parts of the shapes fade in and go over the threshold they appear.&lt;br /&gt;
The blur also means the shapes sort of meld together as we sort earlier with the metaballs effect itself.&lt;/p&gt;
&lt;p&gt;We basically fade from one icon to the other while animating the metaballs effect on and then back off again.&lt;/p&gt;
&lt;p&gt;The result:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/creating-cool-ui-shape-morphing/icon-morph-demo-short.mp4&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;You can change the feel of the effect by changing the speed and overlap of different parts of the animation; and how much blur you fade in during the animation.&lt;br /&gt;
But I&#39;ve found those values to look quite nice and match the style of the effect in the original video reasonably well.&lt;/p&gt;
&lt;h1&gt;Useful Links&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.gamedev.net/tutorials/_/technical/graphics-programming-and-theory/exploring-metaballs-and-isosurfaces-in-2d-r2556/&#34;&gt;Exploring Metaballs and Isosurfaces in 2D&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blobs.webflow.io&#34;&gt;Make and animate Metaballs with Webflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://jamie-wong.com/2014/08/19/metaballs-and-marching-squares/&#34;&gt;Metaballs and Marching Squares&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gist.github.com/robb/094f4f946d7f02c69b65bed19b0f25d9&#34;&gt;GooeyView.swift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rnkyr/metaballs&#34;&gt;Blob effect implementation with UIKIt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/dabing1022/DBMetaballLoading&#34;&gt;Metaball loading written in Swift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/FlexMonkey/Globular&#34;&gt;Colourful SpriteKit Metaballs Controlled by 3D Touch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">UITableViewCell Styles</title>
    <id>https://ikyle.me/blog/2022/uitableviewcellstyles</id>
    <updated>2022-05-29T00:17:59.498373+00:00</updated>
    <published>2022-05-29T00:17:59.498361+00:00</published>
    <link href="https://ikyle.me/blog/2022/uitableviewcellstyles" />
    <summary type="text">Examples of each UITableViewCellStyle and UITableViewCellAccessoryType</summary>
    <content type="html">&lt;p&gt;UITableView has various style options &lt;code&gt;UITableViewCell.CellStyle&lt;/code&gt;, but the documentation doesn&#39;t include any images to show you what each one actually looks like.&lt;/p&gt;
&lt;h2&gt;UITableView Cells&lt;/h2&gt;
&lt;p&gt;When creating a table view cell there are 2 included style options &lt;code&gt;UITableViewCell.AccessoryType&lt;/code&gt; and &lt;code&gt;UITableViewCell.CellStyle&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tableView&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;tableView&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UITableView&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;cellForRowAt&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;indexPath&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;IndexPath&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #5B269A&#34;&gt;UITableViewCell&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;reuseIdentifier&lt;/span&gt; = &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;reuseIdentifier&amp;quot;&lt;/span&gt;
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cell&lt;/span&gt;:&lt;span style=&#34;color: #5B269A&#34;&gt;UITableViewCell&lt;/span&gt;? = &lt;span style=&#34;color: #000&#34;&gt;tableView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dequeueReusableCell&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;withIdentifier&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;reuseIdentifier&lt;/span&gt;)
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cell&lt;/span&gt; == &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt; {
        &lt;span style=&#34;color: #000&#34;&gt;cell&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UITableViewCell&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;style&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UITableViewCell&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;CellStyle&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;default&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;reuseIdentifier&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;reuseIdentifier&lt;/span&gt;)
    }
    
    &lt;span style=&#34;color: #000&#34;&gt;cell&lt;/span&gt;!.&lt;span style=&#34;color: #000&#34;&gt;imageView&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;named&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;exampleImage&amp;quot;&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;cell&lt;/span&gt;!.&lt;span style=&#34;color: #000&#34;&gt;textLabel&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt; = &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Text Label&amp;quot;&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;cell&lt;/span&gt;!.&lt;span style=&#34;color: #000&#34;&gt;detailTextLabel&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt; = &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Detail Label&amp;quot;&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;cell&lt;/span&gt;!.&lt;span style=&#34;color: #000&#34;&gt;accessoryType&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UITableViewCell&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;AccessoryType&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;disclosureIndicator&lt;/span&gt;
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cell&lt;/span&gt;!
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;UITableViewCellStyle&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;All Styles&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;Including Images&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uitableviewcellstyles/UITableViewCellStyle-style.png&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uitableviewcellstyles/UITableViewCellStyle-styles_with_image.png&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;All styles &amp;amp; accessories&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;All styles, Images &amp;amp; accessories&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uitableviewcellstyles/UITableViewCellStyle-styles_without_image-AccessoryTypes.png&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uitableviewcellstyles/UITableViewCellStyle-styles_with_image-AccessoryTypes.png&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;UITableViewCellAccessoryType&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;All Styles&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;With Images&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uitableviewcellstyles/UITableViewCellAccessoryType.png&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uitableviewcellstyles/UITableViewCellAccessoryType_with_images.png&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Understanding UIKit: CALayer Anchor Point</title>
    <id>https://ikyle.me/blog/2022/understanding-uikit-calayer-anchor-point</id>
    <updated>2022-04-26T02:11:47.947967+00:00</updated>
    <published>2022-04-26T02:11:47.947967+00:00</published>
    <link href="https://ikyle.me/blog/2022/understanding-uikit-calayer-anchor-point" />
    <summary type="text">Understanding how the CALayer.anchorPoint property effects layout</summary>
    <content type="html">&lt;p&gt;All on screen elements (normally) on iOS are made of &lt;code&gt;UIView&lt;/code&gt;&#39;s. They have a frame, which have an &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; origin, and a &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; size.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;frame&lt;/code&gt; is actually a derived property. It comes from combining the &lt;code&gt;bounds&lt;/code&gt;, &lt;code&gt;center&lt;/code&gt; and &lt;code&gt;transform&lt;/code&gt; properties.  If you apply a &lt;code&gt;transform&lt;/code&gt; to scale a view up by 2 its &lt;code&gt;frame&lt;/code&gt; will grow, but its &lt;code&gt;bounds&lt;/code&gt; will remain the same size.&lt;br /&gt;
The bounds is the view&#39;s internal size, the frame is the external size.&lt;/p&gt;
&lt;h1&gt;UIView Frame&lt;/h1&gt;
&lt;p&gt;You can see in this example, applying the scaling changes the &lt;code&gt;frame&lt;/code&gt;, but not the &lt;code&gt;bounds&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/understanding-uikit-calayer-anchor-point/AnchorPointDemo_frame.png&#34; alt=&#34;2 example UIViews with the same bounds but different frames due to the transform&#34; /&gt;&lt;/p&gt;
&lt;p&gt;However, UIView&#39;s are actually a helper which adds touch and other event handling on top of the real on screen element. The real element which controls what appears on screen is the &lt;code&gt;CALayer&lt;/code&gt; which backs each and every &lt;code&gt;UIView&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;CALayer&#39;s location on screen is controlled by very similar, but slightly different set of properties.&lt;br /&gt;
The &lt;code&gt;bounds&lt;/code&gt;, for internal size.&lt;br /&gt;
The &lt;code&gt;position&lt;/code&gt;, its location in its parent view.&lt;br /&gt;
The &lt;code&gt;transform&lt;/code&gt;, which is a 3D transform now (not just a 2D one like UIView&#39;s &lt;code&gt;transform&lt;/code&gt; property)&lt;/p&gt;
&lt;p&gt;And finally the &lt;code&gt;anchorPoint&lt;/code&gt; property.&lt;/p&gt;
&lt;h1&gt;CALayer anchorPoint&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/understanding-uikit-calayer-anchor-point/AnchorPointDemo_Start.png&#34; alt=&#34;3 example UIViews with different anchor points&#34; /&gt;&lt;/p&gt;
&lt;p&gt;By default &lt;code&gt;anchorPoint&lt;/code&gt; is &lt;code&gt;(x: 0.5, y: 0.5)&lt;/code&gt;. This is a relative value.&lt;br /&gt;
Instead of being a point value in the parent view/layer like position or bounds, this is relative to its width and height.&lt;br /&gt;
&lt;code&gt;(x:0, y:0)&lt;/code&gt; is the top left corner.&lt;br /&gt;
&lt;code&gt;(x:0.5, y:0.5)&lt;/code&gt; the middle of the view.&lt;br /&gt;
&lt;code&gt;(x:1, y: 1)&lt;/code&gt; is the bottom right edge of the view.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;anchorPoint&lt;/code&gt; is the location inside the view/layer that the &lt;code&gt;UIView.center&lt;/code&gt; or &lt;code&gt;CALayer.position&lt;/code&gt; represents. Normally this defaults to the centre of the view.&lt;br /&gt;
However, if you change the anchor point the entire layer shifts over based on this pivot point.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/understanding-uikit-calayer-anchor-point/AnchorPointDemo_Normal.png&#34; alt=&#34;3 example UIViews with different anchor points&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The official documentation puts it nicely.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All geometric manipulations to the view occur about the specified point. For example, applying a rotation transform to a layer with the default anchor point causes the layer to rotate around its center. Changing the anchor point to a different location would cause the layer to rotate around that new point.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This hints at one of the big reasons you might want to use the anchorPoint property. It allows you to easily control how other properties, such as the transform (rotation and scaling) are applied to the views.&lt;/p&gt;
&lt;p&gt;If we apply the same rotation to all the views from the above example you can see the different effects it has on each.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/understanding-uikit-calayer-anchor-point/AnchorPointDemo_Rotation.png&#34; alt=&#34;3 example UIViews with different anchor points with rotation applied&#34; /&gt;&lt;/p&gt;
&lt;h1&gt;Outside the Box&lt;/h1&gt;
&lt;p&gt;All the examples above have the &lt;code&gt;anchorPoint&lt;/code&gt; inside the view&#39;s &lt;code&gt;bounds&lt;/code&gt;, as we would normally expect, with values between 0 and 1. However, there is nothing that requires that.&lt;br /&gt;
We can freely set values above, or below, that range.&lt;/p&gt;
&lt;p&gt;An X value of 1 is the full width away from the left edge. So a value of 2 would be 2 full widths of the layer away from the left edge.&lt;/p&gt;
&lt;p&gt;Similarly a value of -1 would be 1 full width away from the left edge of the layer.&lt;/p&gt;
&lt;p&gt;All the examples below have the same centre point set, but different anchor points (and some rotation).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/understanding-uikit-calayer-anchor-point/AnchorPointDemo_out_of_the_box.png&#34; alt=&#34;4 example UIViews with the same centre and size but different anchor points and rotation&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Although these examples use rotation, the same also applies to scaling. Scaling up or down a layer is done from the point of origin of its &lt;code&gt;anchorPoint&lt;/code&gt;.&lt;br /&gt;
So if the anchorPoint is outside the bounds, when a layer is scaled up it will appear to expand away from the &lt;code&gt;anchorPoint&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">UIBlurEffectStyles</title>
    <id>https://ikyle.me/blog/2022/uiblureffectstyle</id>
    <updated>2022-04-01T00:58:43.764302+00:00</updated>
    <published>2022-04-01T00:58:43.764283+00:00</published>
    <link href="https://ikyle.me/blog/2022/uiblureffectstyle" />
    <summary type="text">Examples of the different available UIBlurEffectStyle types</summary>
    <content type="html">&lt;p&gt;Blurred backgrounds are a staple of modern app design, and the Apple provided UIView to achieve that effect with is via applying a &lt;code&gt;UIBlurEffect&lt;/code&gt; to a &lt;code&gt;UIVisualEffectView&lt;/code&gt; view.&lt;/p&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;visualEffectView&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIVisualEffectView&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;effect&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addSubview&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;visualEffectView&lt;/span&gt;)

&lt;span style=&#34;color: #000&#34;&gt;visualEffectView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;effect&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIBlurEffect&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;style&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;systemMaterial&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;UIVisualEffectView&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*visualEffectView&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #5B269A&#34;&gt;UIVisualEffectView&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;initWithEffect:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;];
[&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;addSubview:visualEffectView&lt;/span&gt;];

&lt;span style=&#34;color: #000&#34;&gt;visualEffectView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;effect&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;UIBlurEffect&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;effectWithStyle:UIBlurEffectStyleSystemMaterial&lt;/span&gt;];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;UIBlurEffectStyle&lt;/h1&gt;
&lt;p&gt;The different blur effects available are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The original 4 from iOS 8&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleExtraLight&lt;/span&gt;, &lt;span style=&#34;color: #177500&#34;&gt;// .extraLight&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleLight&lt;/span&gt;,      &lt;span style=&#34;color: #177500&#34;&gt;// .light&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleDark&lt;/span&gt;,       &lt;span style=&#34;color: #177500&#34;&gt;// .dark&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleExtraDark&lt;/span&gt;   &lt;span style=&#34;color: #177500&#34;&gt;// .extraDark&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Adaptive Default Styles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These will adapt to light and dark mode automatically as the user switches modes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleRegular&lt;/span&gt;,  &lt;span style=&#34;color: #177500&#34;&gt;// .regular&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleProminent&lt;/span&gt; &lt;span style=&#34;color: #177500&#34;&gt;// .prominent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Adaptive Styles&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleSystemUltraThinMaterial&lt;/span&gt; &lt;span style=&#34;color: #177500&#34;&gt;// .systemUltraThinMaterial&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleSystemThinMaterial&lt;/span&gt;      &lt;span style=&#34;color: #177500&#34;&gt;// .systemThinMaterial&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleSystemMaterial&lt;/span&gt;          &lt;span style=&#34;color: #177500&#34;&gt;// .system&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleSystemThickMaterial&lt;/span&gt;     &lt;span style=&#34;color: #177500&#34;&gt;// .systemThickMaterial&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;UIBlurEffectStyleSystemChromeMaterial&lt;/span&gt;.   &lt;span style=&#34;color: #177500&#34;&gt;// .ystemChromeMaterial&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Example Project&lt;/h1&gt;
&lt;p&gt;To visualise these different styles I creates &lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/tree/master/Blur%20Effects%20ObjC&#34;&gt;an example project&lt;/a&gt; which allows you to see each style in light and dark mode.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;left&#34;&gt;&lt;/th&gt;
&lt;th align=&#34;left&#34;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;left&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/Basic_Test_App.jpg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;left&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/Options.jpg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Screenshots&lt;/h1&gt;
&lt;p&gt;Examples of each UIBlurEffectStyle style&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;UIBlurEffectStyle&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;Screenshot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;ExtraLight&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;Light&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/ExtraLight.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/Light.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;Dark&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;Regular&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/Dark.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/Regular.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;Prominent&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;SystemUltraThinMaterial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/Prominent.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemUltraThinMaterial.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;SystemThinMaterial&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;SystemMaterial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemThinMaterial.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemMaterial.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;SystemThickMaterial&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;SystemChromeMaterial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemThickMaterial.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemChromeMaterial.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;SystemUltraThinMaterialLight&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;SystemThinMaterialLight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemUltraThinMaterialLight.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemThinMaterialLight.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;SystemMaterialLight&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;SystemThickMaterialLight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemMaterialLight.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemThickMaterialLight.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;SystemChromeMaterialLight&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;SystemUltraThinMaterialDark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemChromeMaterialLight.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemUltraThinMaterialDark.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;SystemThinMaterialDark&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;SystemMaterialDark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemThinMaterialDark.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemMaterialDark.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;SystemThickMaterialDark&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;SystemChromeMaterialDark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemThickMaterialDark.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2022/uiblureffectstyle/SystemChromeMaterialDark.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Spherium 1.0 Released</title>
    <id>https://ikyle.me/blog/2020/spherium-release</id>
    <updated>2020-08-13T14:33:58.135459+00:00</updated>
    <published>2020-08-13T14:33:58.135459+00:00</published>
    <link href="https://ikyle.me/blog/2020/spherium-release" />
    <summary type="text">Spherium is designed to view and share 360 images in your Photo Library</summary>
    <content type="html">&lt;p&gt;&lt;a href=&#34;https://spherium.app&#34;&gt;Spherium&lt;/a&gt; lets you easily view and share 360 degree images saved in your Photo Library.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/spherium-release/scaled-iPad_Pro_(11-inch)_(2nd_generation)-landscape-4ShareSnapshot169_framed.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Spherium will automatically attempt to find the 360º images saved in your photo library from 360º cameras, drones, apps such as Google Street View, or anywhere else you have saved them from.&lt;br /&gt;
It looks for equirectangular images, images with a width equal to twice its height, with a minimum resolution of 1000 x 2000 pixels and lets you view and share those 360 photos.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/spherium-release/iPadPro_(11-inch)_PhotoView.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h1&gt;Sharing&lt;/h1&gt;
&lt;p&gt;Spherium supports taking and sharing normal FOV (field of view) snapshots of 360º images, as if you had taken a normal photo.&lt;br /&gt;
This is really helpful for sharing the photos with friends and posting them on social media, like Instagram.&lt;/p&gt;
&lt;p&gt;You can take one 360º photo and then take a portrait snapshot of it, for posting as an Instagram Story or WhatsApp status, and then also take a landscape or square snapshot for use in a normal Instagram post from the same photo.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/spherium-release/iPadPro_(11-inch)_Share_Snapshot_9-16.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h1&gt;1.0 Released Now&lt;/h1&gt;
&lt;p&gt;Spherium 1.0 is out now available on the iOS App Store.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://apps.apple.com/us/app/id1502130060&#34;&gt;App Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://spherium.app&#34;&gt;Offical Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://spherium.app/faq/&#34;&gt;FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://spherium.app/presskit.zip&#34;&gt;Download PressKit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Inspiration&lt;/h1&gt;
&lt;p&gt;I love taking 360º group photos with friends, or of beautiful landscapes. Capturing the entire view all around me. Taking a photo, not just of me and my friends filling the cameras view, but also of our surroundings and the view all around us.&lt;/p&gt;
&lt;p&gt;You can take 360º photos with dedicated cameras, such as the &lt;a href=&#34;https://www.samsung.com/global/galaxy/gear-360/&#34;&gt;Samsung Gear 360&lt;/a&gt;, &lt;a href=&#34;https://www.kandaovr.com/qoocam-8k/&#34;&gt;Qoocam 8k&lt;/a&gt;, &lt;a href=&#34;https://www.insta360.com/product/insta360-oner_twin-edition/&#34;&gt;Insta360 One R&lt;/a&gt;, &lt;a href=&#34;https://www.insta360.com/product/insta360-onex/&#34;&gt;Insta360 One X&lt;/a&gt;, &lt;a href=&#34;https://gopro.com/en/us/shop/cameras/max/CHDHZ-201-master.html&#34;&gt;Go Pro Fusion and GoPro Max&lt;/a&gt;; drones some drones, such as the &lt;a href=&#34;https://store.dji.com/guides/spark-sphere-mode-review/&#34;&gt;DJI Mavic Air and Spark&lt;/a&gt;; or even on your phone with apps, like &lt;a href=&#34;https://apps.apple.com/gb/app/google-street-view/id904418768&#34;&gt;Google&#39;s Street View app&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;360º cameras and drones usually have companion apps for the devices that support downloading and viewing the photos they take. The problem is then you end up with a photo collection split across various different apps, some in one app, some in another, and your normal photos in your phones Photo Library. Then there is also the risk the manufacture &lt;a href=&#34;https://apps.apple.com/gb/app/samsung-gear-360/id1214791825&#34;&gt;discontinues support for the app&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The apps always have the ability to save the 360º photos to your Photo Library. Unfortunately iOS does not support viewing 360º photos natively, only showing the raw stretched image itself.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/spherium-release/Country_Side.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;I wanted to be able to save all my photos, wherever they came from, in one place and view them all. That&#39;s why I built Spherium.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/spherium-release/scaled-iPadPro_(11-inch)-Info.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://apps.apple.com/us/app/id1502130060&#34;&gt;Spherium 1.0 is available now&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">How to Read and Write Image File Metadata with CoreGraphics</title>
    <id>https://ikyle.me/blog/2020/ios-metadata-from-file</id>
    <updated>2020-07-13T23:33:52.630391+00:00</updated>
    <published>2020-07-13T23:33:52.630391+00:00</published>
    <link href="https://ikyle.me/blog/2020/ios-metadata-from-file" />
    <summary type="text">How to read and write Exif, GPS, IPTC, JFIF, TIFF and other metadata in image files using the CoreGraphics APIs on iOS and macOS</summary>
    <content type="html">&lt;p&gt;UIImage allows you to very easily save the image to a file, with &lt;a href=&#34;https://stackoverflow.com/questions/4623931/get-underlying-nsdata-from-uiimage&#34;&gt;&lt;code&gt;UIImagePNGRepresentation(_)&lt;/code&gt; or &lt;code&gt;UIImageJPEGRepresentation(_, _)&lt;/code&gt;&lt;/a&gt;. However, those new images will be new, with the current date and time, and containing no location or camera metadata. If you want to edit an image without losing those things, that&#39;s a problem.&lt;/p&gt;
&lt;p&gt;Fortunately, CoreGraphics includes several easy API&#39;s to access the image files raw metadata.&lt;br /&gt;
However, these are the raw values themselves. If you want a CLLocation object from the GPS metadata you&#39;ll have to parse the &lt;code&gt;&#34;{GPS}&#34;&lt;/code&gt; section of the metadata yourself.&lt;/p&gt;
&lt;h1&gt;Read Image Metadata&lt;/h1&gt;
&lt;p&gt;To read the image file metadata you only need a CoreGraphics image source and then to pass that image source to the &lt;a href=&#34;https://developer.apple.com/documentation/imageio/1465363-cgimagesourcecopypropertiesatind&#34;&gt;&lt;code&gt;CGImageSourceCopyPropertiesAtIndex(_, _, _)&lt;/code&gt;&lt;/a&gt; function. This function then returns a &lt;code&gt;CFDictionaryRef&lt;/code&gt;, which can just be cast to an &lt;code&gt;NSDictionary&lt;/code&gt;, containing all the image file metadata.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;CGImageSourceRef&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGImageSourceCreateWithURL&lt;/span&gt;((&lt;span style=&#34;color: #A90D91&#34;&gt;__bridge&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CFURLRef&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;NULL&lt;/span&gt;);
&lt;span style=&#34;color: #000&#34;&gt;CFDictionaryRef&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;imageProperties&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGImageSourceCopyPropertiesAtIndex&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;NULL&lt;/span&gt;);
&lt;span style=&#34;color: #000&#34;&gt;NSLog&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;%@&amp;quot;&lt;/span&gt;, (&lt;span style=&#34;color: #A90D91&#34;&gt;__bridge&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;NSDictionary&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)(&lt;span style=&#34;color: #000&#34;&gt;imageProperties&lt;/span&gt;) );
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;data&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;NSData&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;contentsOf&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGImageSourceCreateWithData&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CFData&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGImageSourceCopyPropertiesAtIndex&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;
&lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Example Image Metadata&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;{
    &lt;span style=&#34;color: #000&#34;&gt;ColorModel&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;RGB&lt;/span&gt;;
    &lt;span style=&#34;color: #000&#34;&gt;Depth&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;8&lt;/span&gt;;
    &lt;span style=&#34;color: #000&#34;&gt;PixelHeight&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;5632&lt;/span&gt;;
    &lt;span style=&#34;color: #000&#34;&gt;PixelWidth&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;11264&lt;/span&gt;;
    &lt;span style=&#34;color: #000&#34;&gt;ProfileName&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;quot;sRGB&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;IEC61966-2.1&amp;quot;&lt;/span&gt;;
    &lt;span style=&#34;color: #000&#34;&gt;&amp;quot;{Exif&lt;/span&gt;}&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot; =     {&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        ColorSpace = 1;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        DateTimeDigitized = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;2000&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;02&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;12&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;20&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;20&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;14&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        DateTimeOriginal = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;2000&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;02&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;12&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;20&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;20&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;14&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        PixelXDimension = 11264;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        PixelYDimension = 5632;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    };&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&lt;/span&gt;{&lt;span style=&#34;color: #000&#34;&gt;GPS&lt;/span&gt;}&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot; =     {&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Altitude = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;3&lt;/span&gt;.&lt;span style=&#34;color: #3F6E75&#34;&gt;522659318754763&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        AltitudeRef = 0;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Latitude = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;50&lt;/span&gt;.&lt;span style=&#34;color: #3F6E75&#34;&gt;79826333333333&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        LatitudeRef = N;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Longitude = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;0&lt;/span&gt;.&lt;span style=&#34;color: #3F6E75&#34;&gt;5709116666666667&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        LongitudeRef = W;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    };&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&lt;/span&gt;{&lt;span style=&#34;color: #000&#34;&gt;IPTC&lt;/span&gt;}&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot; =     {&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        DateCreated = 20000612;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        DigitalCreationDate = 20000612;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        DigitalCreationTime = 202014;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        TimeCreated = 202014;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    };&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&lt;/span&gt;{&lt;span style=&#34;color: #000&#34;&gt;JFIF&lt;/span&gt;}&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot; =     {&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        DensityUnit = 0;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        JFIFVersion =         (&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            1,&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            0,&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;            1&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        );&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        XDensity = 72;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        YDensity = 72;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    };&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&lt;/span&gt;{&lt;span style=&#34;color: #000&#34;&gt;TIFF&lt;/span&gt;}&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot; =     {&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Make = Apple;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Model = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;iPhone11,1&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;13&lt;/span&gt;.&lt;span style=&#34;color: #3F6E75&#34;&gt;1&lt;/span&gt;.&lt;span style=&#34;color: #3F6E75&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    };&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;}&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;{&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&lt;/span&gt;{&lt;span style=&#34;color: #000&#34;&gt;Exif&lt;/span&gt;}&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot; =     {&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        DateTimeDigitized = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;2010&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;01&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;12&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;20&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;20&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;14&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        DateTimeOriginal = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;2010&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;01&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;12&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;20&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;20&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;14&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    };&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;    &amp;quot;&lt;/span&gt;{&lt;span style=&#34;color: #000&#34;&gt;GPS&lt;/span&gt;}&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot; =     {&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Altitude = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;2&lt;/span&gt;.&lt;span style=&#34;color: #3F6E75&#34;&gt;522659318754763&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        AltitudeRef = 0;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Latitude = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;53&lt;/span&gt;.&lt;span style=&#34;color: #3F6E75&#34;&gt;79826333333333&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        LatitudeRef = N;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;        Longitude = &amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;-1&lt;/span&gt;.&lt;span style=&#34;color: #3F6E75&#34;&gt;5709116666666667&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;quot;;&lt;/span&gt;
        &lt;span style=&#34;color: #000&#34;&gt;LongitudeRef&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;W;&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;};&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Write Image Metadata&lt;/h1&gt;
&lt;p&gt;To write image file metadata is very similar. Provided with an image source, and the type of image file being written to, we can create a new &lt;code&gt;CGImageDestinationRef&lt;/code&gt; and then write in the image and metadata with &lt;a href=&#34;https://developer.apple.com/documentation/imageio/1465143-cgimagedestinationaddimagefromso&#34;&gt;&lt;code&gt;CGImageDestinationAddImageFromSource(_, _, _, _)&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;CGImageSourceRef&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGImageSourceCreateWithURL&lt;/span&gt;((&lt;span style=&#34;color: #A90D91&#34;&gt;__bridge&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CFURLRef&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;NULL&lt;/span&gt;);
&lt;span style=&#34;color: #000&#34;&gt;CFStringRef&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniformTypeIdentifier&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGImageSourceGetType&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt;);

&lt;span style=&#34;color: #000&#34;&gt;CGImageDestinationRef&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;destination&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGImageDestinationCreateWithURL&lt;/span&gt;( (&lt;span style=&#34;color: #A90D91&#34;&gt;__bridge&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CFURLRef&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;uniformTypeIdentifier&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;NULL&lt;/span&gt;);
&lt;span style=&#34;color: #000&#34;&gt;CGImageDestinationAddImageFromSource&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;destination&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, (&lt;span style=&#34;color: #A90D91&#34;&gt;__bridge&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CFDictionaryRef&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt;);
&lt;span style=&#34;color: #000&#34;&gt;CGImageDestinationFinalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;destination&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGImageSourceCreateWithURL&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CFURL&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;),
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uniformTypeIdentifier&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGImageSourceGetType&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; }

&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;destination&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGImageDestinationCreateWithURL&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;url&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CFURL&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;uniformTypeIdentifier&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; }
&lt;span style=&#34;color: #000&#34;&gt;CGImageDestinationAddImageFromSource&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;destination&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;source&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGImageDestinationFinalize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;destination&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Example Projects&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/ios-metadata-from-file/example_app.jpg&#34; alt=&#34;Example project allowing editing of raw image file metadata&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The example code Xcode Projects demonstrating how to use the new API are available on Github:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/blob/master/Read%20Write%20Image%20Metadata%20ObjC/Read%20Write%20Image%20Metadata%20ObjC/ViewController.m#L126&#34;&gt;ObjC Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/blob/master/Read%20Write%20Image%20Metadata%20Swift/Read%20Write%20Image%20Metadata%20Swift/ViewController.swift#L89&#34;&gt;Swift Project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The example projects support iOS and macOS and allow you to select an image file. The XML representation of the metadata dictionary is then loaded into an editable text view. You can then save the edited metadata and export the resulting file.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Using SwiftUI Previews with UIKit and ObjC</title>
    <id>https://ikyle.me/blog/2020/swiftui-previews-uikit-objc</id>
    <updated>2020-07-01T23:34:35.165396+00:00</updated>
    <published>2020-07-01T23:34:35.165396+00:00</published>
    <link href="https://ikyle.me/blog/2020/swiftui-previews-uikit-objc" />
    <summary type="text">How to live(ish) preview UIKit UI written in Objective C</summary>
    <content type="html">&lt;p&gt;One of the main selling points of SwiftUI is the live preview feature in Xcode. However, it is also possible to use this new feature with UIKit views, even ones written in Objective C.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/swiftui-previews-uikit-objc/9-ObjC-Preview.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h1&gt;Xcode Preview Setup Steps&lt;/h1&gt;
&lt;p&gt;Starting with a new Objective C Xcode project. Create a new &lt;code&gt;SwiftUI View&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/swiftui-previews-uikit-objc/4-New-SwiftUI-File.mp4&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Accept the creation of the Objective-C bridging header.&lt;/li&gt;
&lt;li&gt;Add your &lt;code&gt;UIViewController&lt;/code&gt;&#39;s to the bridging header.&lt;/li&gt;
&lt;li&gt;Replace the &lt;code&gt;SwiftUI&lt;/code&gt; view with a SwiftUI &lt;code&gt;UIViewController&lt;/code&gt; wrapper.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;SwiftUI&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;VCRepresentable&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #000&#34;&gt;VCClass&lt;/span&gt;:&lt;span style=&#34;color: #5B269A&#34;&gt;UIViewController&lt;/span&gt;&amp;gt;: &lt;span style=&#34;color: #000&#34;&gt;UIViewControllerRepresentable&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;updateUIViewController&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uiViewController&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIViewController&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;context&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;Context&lt;/span&gt;) {
        &lt;span style=&#34;color: #177500&#34;&gt;// leave this empty&lt;/span&gt;
    }
    
    @&lt;span style=&#34;color: #000&#34;&gt;available&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;iOS&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;13.0&lt;/span&gt;.&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;makeUIViewController&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;context&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;Context&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #5B269A&#34;&gt;UIViewController&lt;/span&gt; {
        &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;VCClass&lt;/span&gt;()
    }
}

&lt;span style=&#34;color: #A90D91&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;SwiftUIView_Previews&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;PreviewProvider&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;previews&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;some&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;View&lt;/span&gt; {
       &lt;span style=&#34;color: #000&#34;&gt;VCRepresentable&lt;/span&gt;&amp;lt;&lt;span style=&#34;color: #000&#34;&gt;ViewController&lt;/span&gt;&amp;gt;()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Xcode won&#39;t auto-refresh any changes made outside of the &lt;code&gt;body&lt;/code&gt; method, so to refresh you need to press the &lt;code&gt;Resume&lt;/code&gt; button or use the keyboard shortcut &lt;code&gt;CMD&lt;/code&gt;+&lt;code&gt;ALT&lt;/code&gt;+&lt;code&gt;P&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Xcode takes a shortcut to reload a SwiftUI view, but needs to do a rebuild for changes made outside of the views body method (like changes to the ObjC file). Fortunately, if you are using ObjC, the rebuild will be fast so that doesn&#39;t matter very much.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/swiftui-previews-uikit-objc/1-ObjC-UI-Preview.mp4&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;For more information on using Xcode Preview&#39;s with UIKit in general &lt;a href=&#34;https://twitter.com/soulchildpls/status/1269912777772003328&#34;&gt;@soulchildpls&lt;/a&gt;&#39;s &#34;&lt;a href=&#34;https://fluffy.es/xcode-previews-uikit/&#34;&gt;Use Xcode Previews with UIKit&lt;/a&gt;&#34; article goes into more details.&lt;/p&gt;
&lt;h2&gt;Example Project&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/tree/master/ObjC%20Live%20Preview&#34;&gt;Objective C Example Project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">SwiftUI Gradient Text</title>
    <id>https://ikyle.me/blog/2020/swiftui-gradient-text</id>
    <updated>2020-06-27T23:04:00.593509+00:00</updated>
    <published>2020-06-27T23:04:00.593509+00:00</published>
    <link href="https://ikyle.me/blog/2020/swiftui-gradient-text" />
    <summary type="text">How to have text with a gradient fill in SwiftUI</summary>
    <content type="html">&lt;p&gt;When searching how to implement gradient text with SwiftUI the solution I found was to use a &lt;code&gt;LinearGradient&lt;/code&gt; and use the &lt;code&gt;Text&lt;/code&gt; element as the mask for that &lt;code&gt;View&lt;/code&gt;. However, &lt;code&gt;LinearGradient&lt;/code&gt; wants to expand to fill all the available space, and all the examples I found used fixed size &lt;code&gt;View&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;The implementation I came up with to implement this for arbitrary fonts and text was to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a transparent text, to set the needed size.&lt;/li&gt;
&lt;li&gt;Put the &lt;code&gt;LinearGradient&lt;/code&gt; into the text&#39;s &lt;code&gt;background&lt;/code&gt; property.&lt;/li&gt;
&lt;li&gt;Place a second, duplicate, &lt;code&gt;Text&lt;/code&gt; element as the mask for the gradient.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/swiftui-gradient-text/gradient_text.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Custom View&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;GradientText&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;View&lt;/span&gt; {
   
   @&lt;span style=&#34;color: #000&#34;&gt;State&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;
   @&lt;span style=&#34;color: #000&#34;&gt;State&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;gradient&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;LinearGradient&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;LinearGradient&lt;/span&gt;(
      &lt;span style=&#34;color: #000&#34;&gt;gradient&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;Gradient&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt;: [.&lt;span style=&#34;color: #000&#34;&gt;white&lt;/span&gt;, .&lt;span style=&#34;color: #000&#34;&gt;gray&lt;/span&gt;]),
      &lt;span style=&#34;color: #000&#34;&gt;startPoint&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;top&lt;/span&gt;,
      &lt;span style=&#34;color: #000&#34;&gt;endPoint&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;bottom&lt;/span&gt;
   )
   
   &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;body&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;some&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;View&lt;/span&gt; {
      &lt;span style=&#34;color: #000&#34;&gt;Text&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;)
      .&lt;span style=&#34;color: #000&#34;&gt;foregroundColor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;Color&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;clear&lt;/span&gt;)
      .&lt;span style=&#34;color: #000&#34;&gt;background&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;gradient&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;mask&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;Text&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;)))
   }
   
}

&lt;span style=&#34;color: #177500&#34;&gt;// Example Usage&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;struct&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;Gradient_Previews&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;PreviewProvider&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;previews&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;some&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;View&lt;/span&gt; {
      &lt;span style=&#34;color: #000&#34;&gt;GradientText&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Hello World Gradient Text!&amp;quot;&lt;/span&gt;)
         .&lt;span style=&#34;color: #000&#34;&gt;font&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;system&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;size&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;50&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;weight&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;bold&lt;/span&gt;))
         .&lt;span style=&#34;color: #000&#34;&gt;padding&lt;/span&gt;()
         .&lt;span style=&#34;color: #000&#34;&gt;background&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;Color&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;black&lt;/span&gt;)
         .&lt;span style=&#34;color: #000&#34;&gt;previewLayout&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;PreviewLayout&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sizeThatFits&lt;/span&gt;)
         .&lt;span style=&#34;color: #000&#34;&gt;padding&lt;/span&gt;()
         .&lt;span style=&#34;color: #000&#34;&gt;previewDisplayName&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Example Gradient Text&amp;quot;&lt;/span&gt;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If anyone knows a better solution without the duplicate &lt;code&gt;Text&lt;/code&gt; element please let me know on &lt;a href=&#34;https://twitter.com/Freerunnering&#34;&gt;Twitter&lt;/a&gt; or &lt;a href=&#34;https://mastodon.technology/@ikyle&#34;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Using PHPickerViewController to Select a Photo on iOS 14</title>
    <id>https://ikyle.me/blog/2020/phpickerviewcontroller</id>
    <updated>2020-06-23T03:59:40.595178+00:00</updated>
    <published>2020-06-23T03:59:40.595178+00:00</published>
    <link href="https://ikyle.me/blog/2020/phpickerviewcontroller" />
    <summary type="text">How to use the new PHPickerViewController class in iOS 14 to select a photo from the Photo Library on iOS in Objective C and Swift</summary>
    <content type="html">&lt;p&gt;For many years the simplest way to selection photos and videos on iOS has been to use the &lt;code&gt;UIImagePickerController&lt;/code&gt; class. The class allowed you to present a built in system UI to select a photo or video and return it to your app, without having to built the selection UI or prompt for access to the photo library.&lt;/p&gt;
&lt;p&gt;The code to do so was incredibly simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Objective C&lt;/span&gt;
- (&lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;pickPhoto&lt;/span&gt;
{
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*imagePicker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;init&lt;/span&gt;];
    &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sourceType&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;UIImagePickerControllerSourceTypePhotoLibrary&lt;/span&gt;;
    &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;delegate&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;;
    [&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;presentViewController:imagePicker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;animated:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;YES&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;completion:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;];
}

&lt;span style=&#34;color: #177500&#34;&gt;// Implement UIImagePickerControllerDelegate method&lt;/span&gt;
-(&lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;imagePickerController:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;didFinishPickingMediaWithInfo:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;NSDictionary&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;UIImagePickerControllerInfoKey&lt;/span&gt;,&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;
{
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*image&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;UIImagePickerControllerOriginalImage&lt;/span&gt;];
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;imageView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;;

    [&lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dismissViewControllerAnimated:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;YES&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;completion:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pickPhoto&lt;/span&gt;()
{
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt;()
    &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sourceType&lt;/span&gt; = .&lt;span style=&#34;color: #000&#34;&gt;photoLibrary&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;delegate&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;present&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;animated&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;)
}

&lt;span style=&#34;color: #177500&#34;&gt;// Implement UIImagePickerControllerDelegate method&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;imagePickerController&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;didFinishPickingMediaWithInfo&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;InfoKey&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;])
{
    &lt;span style=&#34;color: #000&#34;&gt;imageView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;[.&lt;span style=&#34;color: #000&#34;&gt;originalImage&lt;/span&gt;] &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt;? &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dismiss&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;animated&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, &lt;code&gt;UIImagePickerController&lt;/code&gt; had a number of disadvantages: it was fairly basic and limited in the UI it gave the users to browse their library with; it only allowed selection of one item at a time, and had only basic filtering support. Now in iOS 14 the &lt;code&gt;UIImagePickerController&lt;/code&gt; is &#34;soft deprecated&#34;. Although not currently marked as deprecated, if you look at the header file you&#39;ll see the API marker with this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;API_DEPRECATED(&amp;quot;Will be removed in a future release, use PHPicker.&amp;quot;, ios(11, API_TO_BE_DEPRECATED));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;API_TO_BE_DEPRECATED&lt;/code&gt; is defined with this comment above it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;/*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;API_TO_BE_DEPRECATED&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;used&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;version&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;number&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;API&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;that&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;will&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;be&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;deprecated&lt;/span&gt; 
 &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;an&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;upcoming&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;release&lt;/span&gt;. &lt;span style=&#34;color: #000&#34;&gt;This&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;soft&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;deprecation&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;an&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;intermediate&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;step&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;before&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;formal&lt;/span&gt; 
 &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;deprecation&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;notify&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;developers&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;about&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;the&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;API&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;before&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;compiler&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;warnings&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;are&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;generated&lt;/span&gt;.
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Instead iOS 14 now includes the &lt;code&gt;PHPickerViewController&lt;/code&gt; with support for multiple selection and a much improved UI.&lt;/p&gt;
&lt;h1&gt;PHPicker&lt;/h1&gt;
&lt;p&gt;Rather than being in UIKit, the new &lt;code&gt;PHPicker.&lt;/code&gt; classes in iOS 14 are located in the &lt;code&gt;PhotosUI&lt;/code&gt; framework and include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/documentation/photokit/phpickerviewcontroller&#34;&gt;&lt;code&gt;PHPickerViewController&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/documentation/photokit/phpickerconfiguration&#34;&gt;&lt;code&gt;PHPickerConfiguration&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/documentation/photokit/phpickerfilter&#34;&gt;&lt;code&gt;PHPickerFilter&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/documentation/photokit/phpickerresult&#34;&gt;&lt;code&gt;PHPickerResult&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You present a &lt;code&gt;PHPickerViewController&lt;/code&gt;, which has a &lt;code&gt;PHPickerConfiguration&lt;/code&gt; to tell it how many items to select, and of what type. The types of items allowed are defined by the &lt;code&gt;PHPickerConfiguration&lt;/code&gt;&#39;s &lt;code&gt;filter&lt;/code&gt; property, which has the possible option of being any combination of: images, live photos, or videos. How many items users can select is controlled from the &lt;code&gt;PHPickerConfiguration&lt;/code&gt;&#39;s &lt;code&gt;selectionLimit&lt;/code&gt; property.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerConfiguration&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*config&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerConfiguration&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;init&lt;/span&gt;];
&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;selectionLimit&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;3&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;filter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerFilter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;imagesFilter&lt;/span&gt;];

&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerViewController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*pickerViewController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerViewController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;initWithConfiguration:config&lt;/span&gt;];
&lt;span style=&#34;color: #000&#34;&gt;pickerViewController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;delegate&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;;
[&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;presentViewController:pickerViewController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;animated:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;YES&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;completion:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;PHPickerConfiguration&lt;/code&gt;, &lt;code&gt;PHPickerFilter&lt;/code&gt;, and &lt;code&gt;PHPickerResult&lt;/code&gt; all bridge across into Swift as Structs, not as classes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerConfiguration&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;selectionLimit&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;3&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;filter&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;images&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pickerViewController&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerViewController&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;configuration&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;pickerViewController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;delegate&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;present&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;pickerViewController&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;animated&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The results from &lt;code&gt;PHPickerViewController&lt;/code&gt; are returned differently than &lt;code&gt;UIImagePickerController&lt;/code&gt;, instead using &lt;a href=&#34;https://developer.apple.com/documentation/foundation/nsitemprovider&#34;&gt;&lt;code&gt;NSItemProvider&lt;/code&gt;&lt;/a&gt;, like the &lt;a href=&#34;https://developer.apple.com/documentation/uikit/drag_and_drop&#34;&gt;drag and drop APIs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; There is an &lt;code&gt;assetIdentifier&lt;/code&gt; property defined on &lt;code&gt;PHPickerResult&lt;/code&gt;, which is documented as &lt;code&gt;Local identifier of the selected asset&lt;/code&gt;. However, (as of iOS 14.0 beta 1, in my testing this is always nil; regardless of whether or not the app has been granted full photo library access.&lt;br /&gt;
I presume that is meant to be the &lt;a href=&#34;https://developer.apple.com/documentation/photokit/phasset&#34;&gt;&lt;code&gt;PHAsset&lt;/code&gt;&lt;/a&gt;&#39;s &lt;code&gt;assetIdentifier&lt;/code&gt; allowing us to retrieve the asset object with a call to &lt;code&gt;asset = PHAsset.fetchAssets(withLocalIdentifiers: [photoID], options: nil).firstObject&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Objective C&lt;/span&gt;

-(&lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;picker:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerViewController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;didFinishPicking:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;NSArray&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerResult&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;{
   [&lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dismissViewControllerAnimated:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;YES&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;completion:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;];
    
   &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerResult&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*result&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;)
   {
      &lt;span style=&#34;color: #177500&#34;&gt;// Get UIImage&lt;/span&gt;
      [&lt;span style=&#34;color: #000&#34;&gt;result&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;itemProvider&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;loadObjectOfClass:&lt;/span&gt;[&lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;completionHandler:^&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;__kindof&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;NSItemProviderReading&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;_Nullable&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;object&lt;/span&gt;, &lt;span style=&#34;color: #5B269A&#34;&gt;NSError&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;_Nullable&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;)
      {
         &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; ([&lt;span style=&#34;color: #000&#34;&gt;object&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;isKindOfClass:&lt;/span&gt;[&lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt;]])
         {
            &lt;span style=&#34;color: #000&#34;&gt;dispatch_async&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;dispatch_get_main_queue&lt;/span&gt;(), &lt;span style=&#34;color: #000&#34;&gt;^&lt;/span&gt;{
               &lt;span style=&#34;color: #000&#34;&gt;NSLog&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;Selected image: %@&amp;quot;&lt;/span&gt;, (&lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;object&lt;/span&gt;);
            });
         }
      }];
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerViewController&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;didFinishPicking&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerResult&lt;/span&gt;]) {
   &lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dismiss&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;animated&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
   
   &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;result&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;results&lt;/span&gt; {
      &lt;span style=&#34;color: #000&#34;&gt;result&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;itemProvider&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;loadObject&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;ofClass&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completionHandler&lt;/span&gt;: { (&lt;span style=&#34;color: #000&#34;&gt;object&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;error&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;
         &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;object&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt;? &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt; {
            &lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;async&lt;/span&gt; {
               &lt;span style=&#34;color: #177500&#34;&gt;// Use UIImage&lt;/span&gt;
               &lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Selected image: \(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;)&amp;quot;&lt;/span&gt;)
            }
         }
      })
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Filtering and Multi-selection&lt;/h2&gt;
&lt;p&gt;By default the selectionLimit is set to 1, and there is no filter.&lt;/p&gt;
&lt;p&gt;The change what is selected, and how many, we just need to change the &lt;code&gt;PHPickerConfiguration&lt;/code&gt;.&lt;br /&gt;
For example: to select up to 10 videos we would need to specify.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerConfiguration&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*config&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerConfiguration&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;init&lt;/span&gt;];
&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;selectionLimit&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;filter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerFilter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;videosFilter&lt;/span&gt;];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;PHPickerConfiguration&lt;/code&gt;, &lt;code&gt;PHPickerFilter&lt;/code&gt;, and &lt;code&gt;PHPickerResult&lt;/code&gt; all bridge across into Swift as Structs, not as classes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerConfiguration&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;selectionLimit&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;config&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;filter&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;PHPickerFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;videos&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Setting the selection limit to 0 allows selecting as many items as the system supports.&lt;/p&gt;
&lt;h1&gt;Example Projects&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/phpickerviewcontroller/example_project.png&#34; alt=&#34;The 4 screens of the example Xcode project&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The example code Xcode Projects demonstrating how to use the new API are available on Github:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/blob/master/Photo%20Picker%20ObjC/Photo%20Picker%20ObjC/ViewController.m#L99&#34;&gt;ObjC Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/blob/master/Photo%20Picker%20Swift/Photo%20Picker%20Swift/ViewController.swift#L13&#34;&gt;Swift Project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">External Only Shadow on UIView</title>
    <id>https://ikyle.me/blog/2020/calayer-external-only-shadow</id>
    <updated>2020-06-16T03:06:29.634590+00:00</updated>
    <published>2020-06-16T03:06:29.634590+00:00</published>
    <link href="https://ikyle.me/blog/2020/calayer-external-only-shadow" />
    <summary type="text">How to setup a CALayer shadow to show externally only with CALayer shadowPath and maskLayer</summary>
    <content type="html">&lt;p&gt;To add a shadow to a &lt;code&gt;UIView&lt;/code&gt; is very easy, all you have to do is drop down to the &lt;code&gt;CALayer&lt;/code&gt; underneath and setup it&#39;s shadow properties.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/calayer-external-only-shadow/green-shadow.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Objective C&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blackColor&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowOffset&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGSizeMake&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;);
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowRadius&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowOpacity&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowColor&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;black&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;cgColor&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowOffset&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGSize&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;:&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;height&lt;/span&gt;:&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;);
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowRadius&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowOpacity&lt;/span&gt; = &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, if your view has a partially, or completely, transparent background color this can look a bit weird.&lt;/p&gt;
&lt;p&gt;Original:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/calayer-external-only-shadow/original.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// ObjC&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;backgroundColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;colorWithRed:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;green:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blue:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alpha:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;];
&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;backgroundColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;red:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;green:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blue:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;alpha:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With shadow:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/calayer-external-only-shadow/green-transparent-shadow.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;To fix this we can use use &lt;code&gt;CALayer&lt;/code&gt;&#39;s &lt;code&gt;maskLayer&lt;/code&gt; property to create an external only shadow.&lt;/p&gt;
&lt;p&gt;First we need to create a mask layer the size and change of our original view.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// ObjC&lt;/span&gt;
&lt;span style=&#34;color: #5B269A&#34;&gt;CAShapeLayer&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*maskLayer&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;CAShapeLayer&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;];
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;path&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;UIBezierPath&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;bezierPathWithRoundedRect:view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;cornerRadius:view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;cornerRadius&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGPath&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;CAShapeLayer&lt;/span&gt;()
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;path&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIBezierPath&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;roundedRect&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;cornerRadius&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;cornerRadius&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;cgPath&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we need to move it back, up and make it bigger, so it won&#39;t clip the shadow itself.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// ObjC&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;shadowBorder&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowRadius&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;5&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGRectInset&lt;/span&gt;( &lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;-shadowBorder&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;-shadowBorder&lt;/span&gt; ); &lt;span style=&#34;color: #177500&#34;&gt;// Make bigger&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGRectOffset&lt;/span&gt;( &lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;shadowBorder/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;shadowBorder/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt; ); &lt;span style=&#34;color: #177500&#34;&gt;// Move up and left&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Make the mask area bigger than the view, so the shadow itself does not get clipped by the mask&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;shadowBorder&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;shadowRadius&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;5&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;insetBy&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;dx&lt;/span&gt;:  &lt;span style=&#34;color: #000&#34;&gt;-shadowBorder&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;dy&lt;/span&gt;:  &lt;span style=&#34;color: #000&#34;&gt;-shadowBorder&lt;/span&gt;)  &lt;span style=&#34;color: #177500&#34;&gt;// Make bigger&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;offsetBy&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;dx&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;shadowBorder/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;dy&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;shadowBorder/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color: #177500&#34;&gt;// Move up and left&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And set the fill rule to allow cut outs inside the shape.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fillRule&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;kCAFillRuleEvenOdd&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;fillRule&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CAShapeLayerFillRule&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;evenOdd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Lastly we create a mutable path and use it to rewrite the mask layer&#39;s path to one with a cut out a hole in the center where the original view was.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// ObjC&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;CGMutablePathRef&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pathMasking&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGPathCreateMutable&lt;/span&gt;();
&lt;span style=&#34;color: #177500&#34;&gt;// Add the outer view frame&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;CGPathAddPath&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;pathMasking&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;NULL&lt;/span&gt;, [&lt;span style=&#34;color: #5B269A&#34;&gt;UIBezierPath&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;bezierPathWithRect:maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGPath&lt;/span&gt;);
&lt;span style=&#34;color: #177500&#34;&gt;// Translate into the shape back to the smaller original view&amp;#39;s frame start point&lt;/span&gt;
&lt;span style=&#34;color: #5B269A&#34;&gt;CGAffineTransform&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;catShiftBorder&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGAffineTransformMakeTranslation&lt;/span&gt;( &lt;span style=&#34;color: #000&#34;&gt;shadowBorder/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;shadowBorder/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2.0&lt;/span&gt;);
&lt;span style=&#34;color: #177500&#34;&gt;// Now add the original path for the cut out the shape of the original view&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;CGPathAddPath&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;pathMasking&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;NULL&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;CGPathCreateCopyByTransformingPath&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;path&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;&amp;amp;catShiftBorder&lt;/span&gt; ) );
&lt;span style=&#34;color: #177500&#34;&gt;// Set this big rect with a small cutout rect as the mask&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;path&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pathMasking&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pathMasking&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGMutablePath&lt;/span&gt;()
&lt;span style=&#34;color: #177500&#34;&gt;// Add the outer view frame&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;pathMasking&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addPath&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UIBezierPath&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;rect&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;cgPath&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// Translate into the shape back to the smaller original view&amp;#39;s frame start point&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;catShiftBorder&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;CGAffineTransform&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;translationX&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;shadowBorder/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;shadowBorder/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// Now add the original path for the cut out the shape of the original view&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;pathMasking&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addPath&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;path&lt;/span&gt;!.&lt;span style=&#34;color: #000&#34;&gt;copy&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;using&lt;/span&gt;: &amp;amp;&lt;span style=&#34;color: #000&#34;&gt;catShiftBorder&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;!&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// Set this big rect with a small cutout rect as the mask&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;path&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;pathMasking&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Not forgetting to actually set the view&#39;s &lt;code&gt;maskLayer&lt;/code&gt; to our new layer.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;view&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;mask&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;maskLayer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/calayer-external-only-shadow/transparent-shadow.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;This has given us our transparent center, but it will also crop out the background color and any child views placed inside.&lt;br /&gt;
As a result, this technique requires a full size &#39;shadow view&#39; added as the bottom most child of the view you want to add a shadow to, rather than applying this directly to the view itself.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/calayer-external-only-shadow/result.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;h1&gt;Example Xcode Projects&lt;/h1&gt;
&lt;p&gt;There are Swift and Objective C example Xcode projects showing this technique available on Github:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/blob/master/External%20Shadow%20ObjC/External%20Shadow/ViewController.m&#34;&gt;Objective C&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/blob/master/External%20Shadow%20Swift/External%20Shadow%20Swift/ViewController.swift&#34;&gt;Swift&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">CAGradientLayer Explained</title>
    <id>https://ikyle.me/blog/2020/cagradientlayer-explained</id>
    <updated>2020-06-15T22:19:33.819892+00:00</updated>
    <published>2020-06-15T00:22:36.789987+00:00</published>
    <link href="https://ikyle.me/blog/2020/cagradientlayer-explained" />
    <summary type="text">How to natively draw gradients on iOS and macOS with CAGradientLayer</summary>
    <content type="html">&lt;p&gt;iOS and macOS have built in support for drawing gradients in QuartzCore with the CAGradientLayer class, no custom CoreGraphics drawing in &lt;code&gt;-drawRect:&lt;/code&gt; with &lt;a href=&#34;https://stackoverflow.com/questions/9883320/how-to-draw-cggradient-with-specific-color&#34;&gt;&lt;code&gt;CGContextDrawLinearGradient&lt;/code&gt;&lt;/a&gt; necessary.&lt;/p&gt;
&lt;p&gt;Another advantage of CAGradientLayer over using CoreGraphics, besides the convenience, is that CAGradientLayer is rendered directly by the GPU itself. Compared to drawing a gradient with CoreGraphics, which will draw into a buffer on the CPU and then send that to the GPU.&lt;/p&gt;
&lt;h1&gt;Linear (Axial) Gradients&lt;/h1&gt;
&lt;p&gt;To create a linear gradient from one color to another (or several) the layer&#39;s &lt;code&gt;type&lt;/code&gt; must be set to &lt;code&gt;kCAGradientLayerAxial&lt;/code&gt; (&lt;code&gt;.axial&lt;/code&gt;). The colors must be CGColor&#39;s, not UIColors. If you forgot this the color object is just ignored completely.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/cagradientlayer-explained/linear.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Objective C&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;colorWithRed:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;48.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;green:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;35.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blue:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;174.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alpha:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;,
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;colorWithRed:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;200.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;green:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;109.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blue:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;215.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alpha:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt; =
[
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;48.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;35.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;174.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;alpha&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;cgColor&lt;/span&gt;,
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;200.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;109.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;215.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;alpha&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;cgColor&lt;/span&gt;
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Start Point and End Point&lt;/h2&gt;
&lt;p&gt;The start and end point of the gradient can be changed to get different effects.&lt;br /&gt;
For gradients the X and Y axis go from 0 to 1, across their length. So a start point of &lt;code&gt;(x:0, y:0)&lt;/code&gt; is the top left corner.&lt;/p&gt;
&lt;p&gt;By default the start point is &lt;code&gt;(0.5, 0.0)&lt;/code&gt;, half way across the horizontal and at the top vertically, and the end point is the bottom middle of the view &lt;code&gt;(0.5, 1.0)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/cagradientlayer-explained/corners.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/cagradientlayer-explained/linear-horizontal.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The code to create this example horizontal gradient is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Objective C&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Set type (Axial is already the default value)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;kCAGradientLayerAxial&lt;/span&gt;;
&lt;span style=&#34;color: #177500&#34;&gt;// Set the colors (these need to be CGColor&amp;#39;s, not UIColor&amp;#39;s)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;colorWithRed:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;48.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;green:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;35.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blue:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;174.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alpha:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;,
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;colorWithRed:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;200.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;green:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;109.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blue:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;215.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alpha:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;;
&lt;span style=&#34;color: #177500&#34;&gt;// Set the start and end points&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;startPoint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGPointMake&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;);
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;endPoint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGPointMake&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Set type (Axial is already the default value)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;type&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CAGradientLayerType&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;axial&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;// Set the colors (these need to be CGColor&amp;#39;s, not UIColor&amp;#39;s)&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt; =
[
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;48.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;35.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;174.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;alpha&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;cgColor&lt;/span&gt;,
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;200.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;109.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;215.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;alpha&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;cgColor&lt;/span&gt;
]
&lt;span style=&#34;color: #177500&#34;&gt;// Set the start and end points&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;startPoint&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGPoint&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;endPoint&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGPoint&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Multiple Colors and Positions&lt;/h2&gt;
&lt;p&gt;Most of my uses for gradients only involve 2 colors, but the colors property is an array of colors are you can add as many as you want.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/cagradientlayer-explained/rainbow.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blueColor&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;,
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;orangeColor&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;,
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;greenColor&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;,
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;redColor&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;,
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;purpleColor&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;locations&lt;/code&gt; property can be used to control how the colors are distributed across the layer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/cagradientlayer-explained/rainbox-locations.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// ObjC&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;locations&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;
    &lt;span style=&#34;color: #1C01CE&#34;&gt;@0&lt;/span&gt;,   &lt;span style=&#34;color: #177500&#34;&gt;// blueColor&lt;/span&gt;
    &lt;span style=&#34;color: #1C01CE&#34;&gt;@0.1&lt;/span&gt;, &lt;span style=&#34;color: #177500&#34;&gt;// orangeColor&lt;/span&gt;
    &lt;span style=&#34;color: #1C01CE&#34;&gt;@0.6&lt;/span&gt;, &lt;span style=&#34;color: #177500&#34;&gt;// greenColor&lt;/span&gt;
    &lt;span style=&#34;color: #1C01CE&#34;&gt;@0.7&lt;/span&gt;, &lt;span style=&#34;color: #177500&#34;&gt;// redColor&lt;/span&gt;
    &lt;span style=&#34;color: #1C01CE&#34;&gt;@1&lt;/span&gt;    &lt;span style=&#34;color: #177500&#34;&gt;// purpleColor&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;locations&lt;/span&gt; = [
    &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;,   &lt;span style=&#34;color: #177500&#34;&gt;// blueColor&lt;/span&gt;
    &lt;span style=&#34;color: #1C01CE&#34;&gt;0.1&lt;/span&gt;, &lt;span style=&#34;color: #177500&#34;&gt;// orangeColor&lt;/span&gt;
    &lt;span style=&#34;color: #1C01CE&#34;&gt;0.6&lt;/span&gt;, &lt;span style=&#34;color: #177500&#34;&gt;// greenColor&lt;/span&gt;
    &lt;span style=&#34;color: #1C01CE&#34;&gt;0.7&lt;/span&gt;, &lt;span style=&#34;color: #177500&#34;&gt;// redColor&lt;/span&gt;
    &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;    &lt;span style=&#34;color: #177500&#34;&gt;// purpleColor&lt;/span&gt;
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Radial Gradients&lt;/h1&gt;
&lt;p&gt;To create a radial gradient out from one point the layer&#39;s &lt;code&gt;type&lt;/code&gt; must be set to &lt;code&gt;kCAGradientLayerRadial&lt;/code&gt; (&lt;code&gt;.radial&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The start and end point are important, as the &lt;code&gt;endPoint&lt;/code&gt;&#39;s &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; define the end of the second color.&lt;/p&gt;
&lt;p&gt;With a &lt;code&gt;startPoint&lt;/code&gt; in the center &lt;code&gt;(0.5, 0.5)&lt;/code&gt;, an &lt;code&gt;endPoint&lt;/code&gt; &lt;code&gt;x&lt;/code&gt; of 0 or 1 will draw from the center to the outer edge of the view horizontally.&lt;br /&gt;
An &lt;code&gt;endPoint&lt;/code&gt; &lt;code&gt;Y&lt;/code&gt; of 1 or 0 will draw the gradient to the height of the view.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/cagradientlayer-explained/radial-center.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The code to create this example gradient is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Objective C&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Set the type&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;kCAGradientLayerRadial&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;colorWithRed:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;green:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;101.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blue:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alpha:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;,
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;colorWithRed:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;green:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;40.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blue:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;101.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alpha:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;;
&lt;span style=&#34;color: #177500&#34;&gt;// Start in the center&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;startPoint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGPointMake&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;);
&lt;span style=&#34;color: #177500&#34;&gt;// End at the outer edge of the view&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;endPoint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGPointMake&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.75&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// Set type to radial&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;type&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CAGradientLayerType&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;radial&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;// Set the colors&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt; =
[
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;101.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;alpha&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;cgColor&lt;/span&gt;,
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;40.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;101.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;alpha&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;cgColor&lt;/span&gt;
]
&lt;span style=&#34;color: #177500&#34;&gt;// Start point of first color in the middle of the view&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;startPoint&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGPoint&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// End points to the edges of the view&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;endPoint&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGPoint&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.75&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Angular (Conic) Gradients&lt;/h1&gt;
&lt;p&gt;Whereas linear and radial gradients have been part of the iOS SDK forever (iOS 3.0 &amp;amp; 3.2 respectively &amp;amp; macOS 10.6) angular, or conic as CoreAnimation calls them, gradients were only added in iOS 12.0 (macOS 10.14).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;startPoint&lt;/code&gt; is the point at which the gradient is drawn around.&lt;br /&gt;
The &lt;code&gt;endPoint&lt;/code&gt; is the point the start/end line draws aiming at.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/cagradientlayer-explained/conic-labels.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The code to create the example angular gradient is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Objective C&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;kCAGradientLayerConic&lt;/span&gt;;
&lt;span style=&#34;color: #177500&#34;&gt;// Set the colors&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blueColor&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;,
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;colorWithRed:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;50.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;green:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;251.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blue:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alpha:&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;,
    (&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;)[&lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;blackColor&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;CGColor&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;;
&lt;span style=&#34;color: #177500&#34;&gt;// Start point of first color in the middle of the view&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;startPoint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGPointMake&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;);
&lt;span style=&#34;color: #177500&#34;&gt;// End points to the edges of the view&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;endPoint&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;CGPointMake&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;type&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CAGradientLayerType&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;conic&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;colors&lt;/span&gt; =
[
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;blue&lt;/span&gt;,
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;red&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;50.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;green&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;251.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;blue&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;255.0&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;alpha&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;1.0&lt;/span&gt;).&lt;span style=&#34;color: #000&#34;&gt;cgColor&lt;/span&gt;,
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIColor&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;black&lt;/span&gt;
]
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;startPoint&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGPoint&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;endPoint&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;CGPoint&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0.5&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;UIView Wrapper&lt;/h1&gt;
&lt;p&gt;As layers are a little annoying to work with in UIKit, with implicit animations and separate layout, I almost always use it via wrapping it in a custom UIView subclass.&lt;br /&gt;
You can actually change the underlying type of a &lt;code&gt;UIView&lt;/code&gt; by overriding the &lt;code&gt;+(Class)layerClass&lt;/code&gt; class method and returning any CALayer subclass.&lt;/p&gt;
&lt;p&gt;Objective C Subclass. Redefining the layer property means &lt;code&gt;view.layer&lt;/code&gt; will happily return the correct layer class, and &lt;code&gt;@dynamic layer;&lt;/code&gt; tells it not to auto-synthesize anything for that deceleration.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// KHGradientView.h&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;@import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;UIKit&lt;/span&gt;;
&lt;span style=&#34;color: #A90D91&#34;&gt;@import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;QuartzCore&lt;/span&gt;;

&lt;span style=&#34;color: #A90D91&#34;&gt;@interface&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;KHGradientView&lt;/span&gt; : &lt;span style=&#34;color: #5B269A&#34;&gt;UIView&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;@property&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;nonatomic&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;readonly&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;strong&lt;/span&gt;) &lt;span style=&#34;color: #5B269A&#34;&gt;CAGradientLayer&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*layer&lt;/span&gt;;
&lt;span style=&#34;color: #A90D91&#34;&gt;@end&lt;/span&gt;


&lt;span style=&#34;color: #177500&#34;&gt;// KHGradientView.m&lt;/span&gt;
&lt;span style=&#34;color: #633820&#34;&gt;#import &amp;quot;KHGradientView.h&amp;quot;&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;@implementation&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;KHGradientView&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;@dynamic&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt;;

+(&lt;span style=&#34;color: #A90D91&#34;&gt;Class&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;layerClass&lt;/span&gt;{
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;CAGradientLayer&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt;];
}

&lt;span style=&#34;color: #A90D91&#34;&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// Swift Subclass&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;UIKit&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;KHGradientView&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIView&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;override&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;open&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;layerClass&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;AnyClass&lt;/span&gt; {
       &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;CAGradientLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;classForCoder&lt;/span&gt;()
    }
    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;gradientLayer&lt;/span&gt; : &lt;span style=&#34;color: #5B269A&#34;&gt;CAGradientLayer&lt;/span&gt; {
        &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;layer&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt;! &lt;span style=&#34;color: #5B269A&#34;&gt;CAGradientLayer&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It&#39;s incredibly minimal, but makes things much easier in the rest of the codebase.&lt;/p&gt;
&lt;h1&gt;Example Project&lt;/h1&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/cagradientlayer-explained/screenshot.png&#34; alt=&#34;Example linear, radial, and angular gradients&#34; /&gt;&lt;/p&gt;
&lt;p&gt;The example code Xcode Project to produce the opening example image is available on Github:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/blob/master/CAGradientLayer%20Example%20ObjC/CAGradientLayer%20Example%20ObjC/ViewController.m&#34;&gt;ObjC Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/blob/master/CAGradientLayer%20Example%20Swift/CAGradientLayer%20Example%20Swift/ViewController.swift&#34;&gt;Swift Project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Mac Catalyst Title Bar Window Tabs</title>
    <id>https://ikyle.me/blog/2020/mac-catalyst-nstitlebar-tabs</id>
    <updated>2020-06-01T00:57:38.826645+00:00</updated>
    <published>2020-06-01T00:57:38.826645+00:00</published>
    <link href="https://ikyle.me/blog/2020/mac-catalyst-nstitlebar-tabs" />
    <summary type="text">How to setup NSTitleBar style tabs for a UIKit app on mac with Catalyst</summary>
    <content type="html">&lt;p&gt;To start with let&#39;s take a simple tab bar based app with a label in the &lt;code&gt;UIViewController&lt;/code&gt;&#39;s&#39; telling us which view we are looking at.&lt;br /&gt;
If we just take this app and compile and run it for mac Catalyst this is the result we get.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/mac-catalyst-nstitlebar-tabs/AppStartingPoint.png&#34; alt=&#34;Screenshot of the UITabBarController app on iPhone 8 and macOS Catalina&#34; /&gt;&lt;/p&gt;
&lt;p&gt;This works... but feels a lot like running the app in the iOS simulator.&lt;br /&gt;
You probably want to change the style, to either the side tabs, like the AppStore app, or titlebar tabs, like the Calendar app.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/mac-catalyst-nstitlebar-tabs/Apple_Apps_Screenshots.png&#34; alt=&#34;Screenshot of the AppStore and Calendar apps on Catalina&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Most of the time I want the tabs in the titlebar. (if you want the sidebar style, that&#39;s &lt;code&gt;UISplitViewController&lt;/code&gt; + &lt;code&gt;.primaryBackgroundStyle = .sidebar&lt;/code&gt;)&lt;/p&gt;
&lt;h2&gt;Putting Tabs in the Title Bar&lt;/h2&gt;
&lt;p&gt;To do that we need to customise the window&#39;s tilebar. In iOS, the application window (as in multi-window support, not &lt;code&gt;UIWindow&lt;/code&gt; or &lt;code&gt;NSWindow&lt;/code&gt;) is now controlled from the &lt;code&gt;UISceneDelegate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the SceneDelegate we setup an &lt;code&gt;NSToolbar&lt;/code&gt;, set the &lt;code&gt;SceneDelegate&lt;/code&gt; as the &lt;code&gt;NSToolbar&lt;/code&gt; delegate and then hide both the title bar&#39;s title itself, after assigning the toolbar, &amp;amp; the tab bar.&lt;br /&gt;
Inside the SceneDelegate class add:&lt;/p&gt;
&lt;p&gt;Objective-C (ObjC)&lt;br /&gt;
SceneDelegate.m&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// SceneDelegate.m&lt;/span&gt;
&lt;span style=&#34;color: #633820&#34;&gt;#if TARGET_OS_MACCATALYST&lt;/span&gt;
&lt;span style=&#34;color: #633820&#34;&gt;#import &amp;lt;AppKit/AppKit.h&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;@interface&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;SceneDelegate&lt;/span&gt; () &lt;span style=&#34;color: #000&#34;&gt;&amp;lt;NSToolbarDelegate&amp;gt;&lt;/span&gt;
&lt;span style=&#34;color: #633820&#34;&gt;#else&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;@interface&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;SceneDelegate&lt;/span&gt; ()
&lt;span style=&#34;color: #633820&#34;&gt;#endif&lt;/span&gt;


&lt;span style=&#34;color: #177500&#34;&gt;// -[SceneDelegate scene:willConnectToSession:options:] {&lt;/span&gt;

&lt;span style=&#34;color: #633820&#34;&gt;#if TARGET_OS_MACCATALYST&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;NSToolbar&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*toolbar&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;NSToolbar&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;initWithIdentifier:&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;mainToolbar&amp;quot;&lt;/span&gt;];
&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;centeredItemIdentifier&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;mainTabsToolbarItem&amp;quot;&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;delegate&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;;

[(&lt;span style=&#34;color: #5B269A&#34;&gt;UIWindowScene&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;titlebar&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;;
[(&lt;span style=&#34;color: #5B269A&#34;&gt;UIWindowScene&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;titlebar&lt;/span&gt;].&lt;span style=&#34;color: #000&#34;&gt;titleVisibility&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;UITitlebarTitleVisibilityHidden&lt;/span&gt;;

&lt;span style=&#34;color: #000&#34;&gt;_tabbarController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tabBar&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;hidden&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;YES&lt;/span&gt;;
&lt;span style=&#34;color: #633820&#34;&gt;#endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Swift&lt;br /&gt;
SceneDelegate.swift&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// SceneDelegate.swift&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;// SceneDelegate.scene(_:willConnectTo:options:) {&lt;/span&gt;

&lt;span style=&#34;color: #633820&#34;&gt;#if&lt;/span&gt; &lt;span style=&#34;color: #633820&#34;&gt;targetEnvironment&lt;/span&gt;(&lt;span style=&#34;color: #633820&#34;&gt;macCatalyst&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;NSToolbar&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;identifier&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;mainToolbar&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;centeredItemIdentifier&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;NSToolbarItem&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;Identifier&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;rawValue&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;mainTabsToolbarItem&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;delegate&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;windowScene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;titlebar&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;titleVisibility&lt;/span&gt; = .&lt;span style=&#34;color: #000&#34;&gt;hidden&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;windowScene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;titlebar&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;tabBarController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tabBar&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isHidden&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;
&lt;span style=&#34;color: #633820&#34;&gt;#endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Most of the &lt;code&gt;NSToolbar&lt;/code&gt; stuff is actually configured via the &lt;code&gt;NSToolbarDelegate&lt;/code&gt; methods.&lt;/p&gt;
&lt;p&gt;Here we are: specifying which items can be added to the toolbar &lt;code&gt;toolbarDefaultItemIdentifiers:&lt;/code&gt; &amp;amp; &lt;code&gt;toolbarAllowedItemIdentifiers:&lt;/code&gt;; setting up a new &lt;code&gt;NSToolbarItemGroup&lt;/code&gt; item, specifying the segment control in the center of the title bar; and then .&lt;/p&gt;
&lt;p&gt;ObjC&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// SceneDelegate.m&lt;/span&gt;
&lt;span style=&#34;color: #633820&#34;&gt;#pragma mark - NSToolbarDelegate&lt;/span&gt;

&lt;span style=&#34;color: #633820&#34;&gt;#if TARGET_OS_MACCATALYST&lt;/span&gt;

&lt;span style=&#34;color: #177500&#34;&gt;// NSToolbarItemIdentifier is just an NSString*&lt;/span&gt;
- (&lt;span style=&#34;color: #000&#34;&gt;nullable&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;NSToolbarItem&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;toolbar:&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;NSToolbar&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;itemForItemIdentifier:&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;NSToolbarItemIdentifier&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;itemIdentifier&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;willBeInsertedIntoToolbar:&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;BOOL&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;flag&lt;/span&gt;
{
   &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; ([&lt;span style=&#34;color: #000&#34;&gt;itemIdentifier&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;isEqualToString:&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;mainTabsToolbarItem&amp;quot;&lt;/span&gt;]) {
      &lt;span style=&#34;color: #000&#34;&gt;NSToolbarItemGroup&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*group&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #000&#34;&gt;NSToolbarItemGroup&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;groupWithItemIdentifier:itemIdentifier&lt;/span&gt;
                                                       &lt;span style=&#34;color: #000&#34;&gt;titles&lt;/span&gt;:&lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;First&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;Second&amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;
                                                  &lt;span style=&#34;color: #000&#34;&gt;selectionMode&lt;/span&gt;:&lt;span style=&#34;color: #000&#34;&gt;NSToolbarItemGroupSelectionModeSelectOne&lt;/span&gt;
                                                       &lt;span style=&#34;color: #000&#34;&gt;labels&lt;/span&gt;:&lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;First view&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;Second view&amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;
                                                       &lt;span style=&#34;color: #000&#34;&gt;target&lt;/span&gt;:&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;
                                                       &lt;span style=&#34;color: #000&#34;&gt;action&lt;/span&gt;:&lt;span style=&#34;color: #A90D91&#34;&gt;@selector&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;toolbarGroupSelectionChanged:&lt;/span&gt;)];
      [&lt;span style=&#34;color: #000&#34;&gt;group&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;setSelectedIndex:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;];
      &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;group&lt;/span&gt;;
   }
   
   &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;;
}

&lt;span style=&#34;color: #177500&#34;&gt;/* Returns the ordered list of items to be shown in the toolbar by default.   If during initialization, no overriding values are found in the user defaults, or if the user chooses to revert to the default items this set will be used. */&lt;/span&gt;
- (&lt;span style=&#34;color: #5B269A&#34;&gt;NSArray&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;NSToolbarItemIdentifier&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;toolbarDefaultItemIdentifiers:&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;NSToolbar&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;{
   &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;mainTabsToolbarItem&amp;quot;&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;;
}

&lt;span style=&#34;color: #177500&#34;&gt;/* Returns the list of all allowed items by identifier.  By default, the toolbar does not assume any items are allowed, even the separator.  So, every allowed item must be explicitly listed.  The set of allowed items is used to construct the customization palette.  The order of items does not necessarily guarantee the order of appearance in the palette.  At minimum, you should return the default item list.*/&lt;/span&gt;
- (&lt;span style=&#34;color: #5B269A&#34;&gt;NSArray&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;NSToolbarItemIdentifier&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;toolbarAllowedItemIdentifiers:&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;NSToolbar&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;{
   &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; [&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbarDefaultItemIdentifiers:toolbar&lt;/span&gt;];
}


-(&lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;toolbarGroupSelectionChanged:&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;NSToolbarItemGroup*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;sender&lt;/span&gt;{
   &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tabbarController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;selectedIndex&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sender&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;selectedIndex&lt;/span&gt;;
}
&lt;span style=&#34;color: #633820&#34;&gt;#endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Swift&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;// SceneDelegate.swift&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;// MARK: - NSToolbarDelegate&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;NSToolbar&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;itemForItemIdentifier&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;itemIdentifier&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;NSToolbarItem&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;Identifier&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;willBeInsertedIntoToolbar&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;flag&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color: #5B269A&#34;&gt;NSToolbarItem&lt;/span&gt;?
{
   &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;itemIdentifier&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rawValue&lt;/span&gt; == &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;mainTabsToolbarItem&amp;quot;&lt;/span&gt;)
   {
      &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;group&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;NSToolbarItemGroup&lt;/span&gt;.&lt;span style=&#34;color: #A90D91&#34;&gt;init&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;itemIdentifier&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;itemIdentifier&lt;/span&gt;,
                                 &lt;span style=&#34;color: #000&#34;&gt;titles&lt;/span&gt;: [&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;First&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Second&amp;quot;&lt;/span&gt;],
                                 &lt;span style=&#34;color: #000&#34;&gt;selectionMode&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;selectOne&lt;/span&gt;,
                                 &lt;span style=&#34;color: #000&#34;&gt;labels&lt;/span&gt;: [&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;First view&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Second view&amp;quot;&lt;/span&gt;],
                                 &lt;span style=&#34;color: #000&#34;&gt;target&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;action&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;#selector&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;toolbarGroupSelectionChanged&lt;/span&gt;))
      &lt;span style=&#34;color: #000&#34;&gt;group&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setSelected&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;at&lt;/span&gt;: &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;)
      &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;group&lt;/span&gt;
   }
   
   &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;
}

&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbarDefaultItemIdentifiers&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;NSToolbar&lt;/span&gt;) -&amp;gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;NSToolbarItem&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;Identifier&lt;/span&gt;] {
   &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;NSToolbarItem&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;Identifier&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;rawValue&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;mainTabsToolbarItem&amp;quot;&lt;/span&gt;)]
}

&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbarAllowedItemIdentifiers&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;NSToolbar&lt;/span&gt;) -&amp;gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;NSToolbarItem&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;Identifier&lt;/span&gt;] {
   &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;toolbarDefaultItemIdentifiers&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;toolbar&lt;/span&gt;)
}


&lt;span style=&#34;color: #A90D91&#34;&gt;@objc&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;toolbarGroupSelectionChanged&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;sender&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;NSToolbarItemGroup&lt;/span&gt;) {
   &lt;span style=&#34;color: #000&#34;&gt;tabBarController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;selectedIndex&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;sender&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;selectedIndex&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you get the error message:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;ERROR:&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Declaration&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;of&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;NSToolbarItemGroup&amp;#39;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;must&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;be&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;imported&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;module&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;AppKit.NSToolbarItemGroup&amp;#39;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;before&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;it&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;required&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This means you haven&#39;t imported the Appkit headers. The fix is to &lt;code&gt;@include AppKit;&lt;/code&gt; or &lt;code&gt;#import &amp;lt;AppKit/AppKit.h&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Before and after:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/mac-catalyst-nstitlebar-tabs/Before_After.png&#34; alt=&#34;Example application using UITabBarController and NSToolbar&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Looking at the result it might seem that the &lt;code&gt;labels&lt;/code&gt; part has been ignored. However, if you turn on customisation of the toolbar, or remove the hiding of the title in the titlebar you can see where it is used.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/mac-catalyst-nstitlebar-tabs/Toolbar_customisation.png&#34; alt=&#34;Example application with visible title, visible labels and customisation panel open&#34; /&gt;&lt;/p&gt;
&lt;h2&gt;Example Projects&lt;/h2&gt;
&lt;p&gt;Made with Xcode 11.4.1 targeting iOS 13.0&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/tree/master/macCatalyst-titlebar-tabbar-objc&#34;&gt;Minimal Objective C Example Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kylehowells/ikyle.me-code-examples/tree/master/macCatalyst-titlebar-tabbar-swift&#34;&gt;Minimal Swift 4 Example Project&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Spherium: 360º Photos Explained</title>
    <id>https://ikyle.me/blog/2020/spherium-360-photos-explained</id>
    <updated>2020-04-27T23:29:15.119018+00:00</updated>
    <published>2020-04-27T23:29:15.119018+00:00</published>
    <link href="https://ikyle.me/blog/2020/spherium-360-photos-explained" />
    <summary type="text">How Spherium detects 360º photos on iOS</summary>
    <content type="html">&lt;p&gt;Spherium searches your iOS library for 360º degree equirectangular images.&lt;/p&gt;
&lt;h1&gt;What is a 360 Photo&lt;/h1&gt;
&lt;p&gt;Example&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/portfolio/spherium/sample_photos/city-small.jpg&#34; alt=&#34;Ariel 360 photo of a city&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Normal photos capture what is in front of the camera. 360º photos capture what is all around the camera in every direction.&lt;/p&gt;
&lt;h1&gt;What is Equirectangular Projection&lt;/h1&gt;
&lt;p&gt;The &lt;a href=&#34;https://en.wikipedia.org/wiki/Equirectangular_projection&#34;&gt;wikipedia article&lt;/a&gt; on it has more information, but effectively a equirectangular projection is a way of stretching the pixels from a sphere into a flat 2:1 image.&lt;/p&gt;
&lt;p&gt;Spherium looks for images which are exactly 2:1 (w:h) to indicate they might be a 360º photo.&lt;/p&gt;
&lt;h1&gt;How Do I Take a 360 Photo&lt;/h1&gt;
&lt;p&gt;You typically use a dedicated &lt;a href=&#34;https://thewirecutter.com/reviews/best-360-degree-camera/&#34;&gt;360 camera&lt;/a&gt;, which captures 2 images from fisheye lens at slightly more than 180 degrees and then merges the two images into one 360 image.&lt;br /&gt;
However, there are also &lt;a href=&#34;https://apps.apple.com/gb/app/google-street-view/id904418768&#34;&gt;apps&lt;/a&gt; that can stitch together a 360º panorama on device from a series of photos in different directions; and several drones have a &lt;a href=&#34;https://store.dji.com/guides/spark-sphere-mode-review/&#34;&gt;360 panorama capture mode&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;How Do I View 360 Photos&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://spherium.app&#34;&gt;Spherium&lt;/a&gt; automatically shows you all the 360 photos in your iOS Photo Library and allows you to view them on your iPhone or iPad.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">iOS 13 UIContextMenu UIMenu UIAction</title>
    <id>https://ikyle.me/blog/2017/ios13-uicontextmenu</id>
    <updated>2020-04-26T00:22:31.834634+00:00</updated>
    <published>2020-04-26T00:22:31.834626+00:00</published>
    <link href="https://ikyle.me/blog/2017/ios13-uicontextmenu" />
    <summary type="text">Stand alone, UICollectionView and UITableView</summary>
    <content type="html">&lt;p&gt;Example opening paragraph&lt;/p&gt;
&lt;h2&gt;Example sub heading&lt;/h2&gt;
&lt;p&gt;Example content.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">How to Add Chapters to MP4s with FFmpeg</title>
    <id>https://ikyle.me/blog/2020/add-mp4-chapters-ffmpeg</id>
    <updated>2020-04-14T23:54:50.491259+00:00</updated>
    <published>2020-04-14T23:54:50.491259+00:00</published>
    <link href="https://ikyle.me/blog/2020/add-mp4-chapters-ffmpeg" />
    <summary type="text">How to add chapters to MP4s, with a helper python script to write the chapter metadata</summary>
    <content type="html">&lt;p&gt;FFmpeg is one of those pieces of software which can do practically anything, &lt;a href=&#34;https://ikyle.me/blog/2020/useful-ffmpeg-video-commands&#34;&gt;if you can work out how&lt;/a&gt;. One of the things it can do it modify the metadata for video files, including adding chapters.&lt;/p&gt;
&lt;p&gt;Adding chapters is done via &lt;a href=&#34;https://ffmpeg.org/ffmpeg-formats.html#Metadata-1&#34;&gt;ffmetadata files&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Extract Metadata From Video&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i INPUT.mp4 -f ffmetadata FFMETADATAFILE.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Example output from a video file with no chapters.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;;FFMETADATA1&lt;/span&gt;
title=None
major_brand=isom
minor_version=512
compatible_brands=isomiso2avc1mp41
encoder=Lavf57.56.100
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Write Metadata To Video&lt;/h2&gt;
&lt;p&gt;The metadata file can be edited, including to add chapters, and then ffmpeg can write it to a file.&lt;br /&gt;
ffmpeg doesn&#39;t mutate the existing video, but instead creates a new video with the new metadata, existing video and existing audio.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i INPUT.mp4 -i FFMETADATAFILE.txt -map_metadata &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt; -codec copy OUTPUT.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Adding Chapters&lt;/h2&gt;
&lt;p&gt;Adding chapters is done by editing the metadata file and adding sections like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;[CHAPTER]&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;TIMEBASE&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;1/1000&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;START&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;1&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;END&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;448000&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;The Pledge&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;[CHAPTER]&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;TIMEBASE&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;1/1000&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;START&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;448001&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;END&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;3883999&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;The Turn&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;[CHAPTER]&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;TIMEBASE&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;1/1000&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;START&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;3884000&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;END&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;4418000&lt;/span&gt;
&lt;span style=&#34;color: #836C28&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;The Prestige&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;h1&gt;Helper Script&lt;/h1&gt;
&lt;p&gt;The requirement to specify times as an integer of some time base, instead of the 1:30:20.5 style format usually used for displaying time is very off-putting to me, so I created a small helper script so I could just note down times while watching a video and have the metadata entries automatically worked out how my automatically.&lt;/p&gt;
&lt;p&gt;The script has a few requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It expects to be in the same directory as a &#39;chapters.txt&#39; file to read from, and an &#39;FFMETADATAFILE&#39; file to output to.&lt;/li&gt;
&lt;li&gt;It expects the chapters.txt file to be a txt file with 1 chapter per line starting with 0:00:00 style time stamps and followed by the title of the chapter&lt;/li&gt;
&lt;li&gt;It appends to an existing FFMETADATAFILE&lt;/li&gt;
&lt;li&gt;It expects no existing chapters in the file&lt;/li&gt;
&lt;li&gt;It expects an final END chapter which is ignored, but provides the end point for the last real chapter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example Chapters.txt&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;23&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;20&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Start&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;40&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;30&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;First&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Performance&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;40&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;56&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Break&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;04&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;44&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Second&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Performance&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;24&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;45&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Crowd&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Shots&lt;/span&gt;
&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;27&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;45&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Credits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Helper script&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;re&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;chapters&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;list&lt;/span&gt;()

&lt;span style=&#34;color: #A90D91&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;chapters.txt&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;f&lt;/span&gt;:
   &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;line&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;f&lt;/span&gt;:
      &lt;span style=&#34;color: #000&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;re.match&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;r&amp;quot;(\d):(\d{2}):(\d{2}) (.*)&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;line&lt;/span&gt;)
      &lt;span style=&#34;color: #000&#34;&gt;hrs&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;int&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;))
      &lt;span style=&#34;color: #000&#34;&gt;mins&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;int&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;))
      &lt;span style=&#34;color: #000&#34;&gt;secs&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;int&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;x.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;3&lt;/span&gt;))
      &lt;span style=&#34;color: #000&#34;&gt;title&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;x.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;4&lt;/span&gt;)

      &lt;span style=&#34;color: #000&#34;&gt;minutes&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;hrs&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;60&lt;/span&gt;) &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;mins&lt;/span&gt;
      &lt;span style=&#34;color: #000&#34;&gt;seconds&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;secs&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;minutes&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;60&lt;/span&gt;)
      &lt;span style=&#34;color: #000&#34;&gt;timestamp&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;seconds&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;1000&lt;/span&gt;)
      &lt;span style=&#34;color: #000&#34;&gt;chap&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; {
         &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;title&amp;quot;&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;title&lt;/span&gt;,
         &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;startTime&amp;quot;&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;timestamp&lt;/span&gt;
      }
      &lt;span style=&#34;color: #000&#34;&gt;chapters.append&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;chap&lt;/span&gt;)

&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;range&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;len&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;chapters&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;):
   &lt;span style=&#34;color: #000&#34;&gt;chap&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;chapters&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt;]
   &lt;span style=&#34;color: #000&#34;&gt;title&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;chap&lt;/span&gt;[&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;]
   &lt;span style=&#34;color: #000&#34;&gt;start&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;chap&lt;/span&gt;[&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;startTime&amp;#39;&lt;/span&gt;]
   &lt;span style=&#34;color: #000&#34;&gt;end&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;chapters&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;i+&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;][&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;startTime&amp;#39;&lt;/span&gt;]&lt;span style=&#34;color: #000&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;
   &lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;[CHAPTER]&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;TIMEBASE=1/1000&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;START={&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;start&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;END={&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;end&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;title={&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;


&lt;span style=&#34;color: #A90D91&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;open&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;FFMETADATAFILE&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;a&amp;quot;&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;myfile&lt;/span&gt;:
    &lt;span style=&#34;color: #000&#34;&gt;myfile.write&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The script could be modified to remove the need for a &lt;code&gt;chapters.txt&lt;/code&gt; file by swapping line 5&#39;s &lt;code&gt;with. ...&lt;/code&gt; statement for this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;chapters_text&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;0:40:30 First Performance&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;0:40:56 Break&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;... etc ...&lt;/span&gt;
&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;chapters_text.splitlines&lt;/span&gt;()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;h3&gt;Example Usage With a Video With No Chapters&lt;/h3&gt;
&lt;p&gt;Get existing video metadata&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i INPUT.mp4 -f ffmetadata FFMETADATAFILE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Check there are no existing chapters.&lt;br /&gt;
Watch the video, noting chapters into a &lt;code&gt;chapters.txt&lt;/code&gt; file as you go.&lt;br /&gt;
Place &lt;code&gt;FFMETADATAFILE &lt;/code&gt;, &lt;code&gt;chapters.txt&lt;/code&gt;, and the video file in the same directory.&lt;/p&gt;
&lt;p&gt;Run the helper script to append chapters to FFMETADATAFILE.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python3 helper.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Create a new video, copying the video and audio from the original without re-encoding.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i INPUT.mp4 -i FFMETADATAFILE -map_metadata &lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt; -codec copy OUTPUT.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Useful FFmpeg Commands for Sharing Videos</title>
    <id>https://ikyle.me/blog/2020/useful-ffmpeg-video-commands</id>
    <updated>2020-04-27T20:40:55.314617+00:00</updated>
    <published>2020-04-07T17:14:37.597317+00:00</published>
    <link href="https://ikyle.me/blog/2020/useful-ffmpeg-video-commands" />
    <summary type="text">A collection of FFmpeg commands I find helpful when sharing videos</summary>
    <content type="html">&lt;p&gt;A collection of FFmpeg commands I find helpful when working with and sharing videos.&lt;/p&gt;
&lt;h2&gt;Convert to MP4&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i input.avi output.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Convert from MKV to MP4 Without Re-encoding&lt;/h2&gt;
&lt;p&gt;This obviously only works the MKV contains a video that can be put in an MP4, like h264 or h265.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i input.mkv -c copy output.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Or you can specify the audio and video separately (and convert the audio by leaving it out, for example).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i input.mkv -vcodec copy -acodec copy output.mp4
ffmpeg -i input.mkv -vcodec copy output.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Change Framerate&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i input60fps.mp4 -r &lt;span style=&#34;color: #1C01CE&#34;&gt;30&lt;/span&gt; output30fps.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Compress For Sharing On The Web&lt;/h2&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i input.mp4 -c:v libx264 -crf &lt;span style=&#34;color: #1C01CE&#34;&gt;25&lt;/span&gt; -c:a aac -movflags faststart output.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The compression factor is set with &lt;code&gt;-crf&lt;/code&gt;, the ffmpeg default for h264 is 23 and 28 for h265.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With x264 and x265, you can set the values between 0 and 51, where lower values would result in better quality, at the expense of higher file sizes. Higher values mean more compression, but at some point you will notice the quality degradation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Quote for: &lt;a href=&#34;https://slhck.info/video/2017/02/24/crf-guide.html&#34;&gt;CRF Guide (Constant Rate Factor in x264, x265 and libvpx)&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Extract Part of Video&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;-ss&lt;/code&gt; to seek to a part of the video.&lt;br /&gt;
&lt;code&gt;-t&lt;/code&gt; for the duration, or &lt;code&gt;-to&lt;/code&gt; for the time in the video&lt;/p&gt;
&lt;p&gt;The behaviour depends on the position of the &lt;code&gt;-ss&lt;/code&gt; parameter. These commands will extract the 10s from 0:30-0:40 in the original video.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;.mp4 -ss &lt;span style=&#34;color: #1C01CE&#34;&gt;30&lt;/span&gt; -t &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt; -c copy out.mp4
ffmpeg -i &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;.mp4 -ss &lt;span style=&#34;color: #1C01CE&#34;&gt;30&lt;/span&gt; -to &lt;span style=&#34;color: #1C01CE&#34;&gt;40&lt;/span&gt; -c copy out.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, if the seek is placed before the file input then timestamps are relative to the seek and these commands now behave differently from each other. With the first output being the same as the original commands, but the second now extracting the 40s long segment from 0:30 to 1:10 in the video.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -ss &lt;span style=&#34;color: #1C01CE&#34;&gt;30&lt;/span&gt; -i &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;.mp4 -t &lt;span style=&#34;color: #1C01CE&#34;&gt;10&lt;/span&gt; -c copy out.mp4
ffmpeg -ss &lt;span style=&#34;color: #1C01CE&#34;&gt;30&lt;/span&gt; -i &lt;span style=&#34;color: #A90D91&#34;&gt;in&lt;/span&gt;.mp4 -to &lt;span style=&#34;color: #1C01CE&#34;&gt;40&lt;/span&gt; -c copy out.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://superuser.com/a/377407/89838&#34;&gt;How to cut a video - superuser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://trac.ffmpeg.org/wiki/Seeking&#34;&gt;Seeking - FFmpeg wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Extract Audio From Video&lt;/h2&gt;
&lt;p&gt;To extract audio from a video you just have to specify an audio file output and it&#39;ll ignore the video.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i input.mkv output.mp3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To extract the raw audio from an MP4 without re-encoding (this depends on what codec the audio data actually is inside the mp4, for most H264 video mp4 files this is AAC audio).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -i input.mp4 -c copy output.aac
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Extract Frame Image&lt;/h2&gt;
&lt;p&gt;Good for quickly getting a thumbnail of the video.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ffmpeg -ss 00:23:00 -i input.mp4 -frames:v 1 output.jpg
ffmpeg -i input.mp4 -ss 00:00:10.5 -vframes 1 output.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://superuser.com/a/608125/89838&#34;&gt;Meaningful thumbnails for a Video using FFmpeg - superuser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://trac.ffmpeg.org/wiki/Create%20a%20thumbnail%20image%20every%20X%20seconds%20of%20the%20video&#34;&gt;Create a thumbnail image every X seconds of the video - FFmpeg wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">How to Add iOS 13.4 Device Support to Xcode 11.3 on Mojave</title>
    <id>https://ikyle.me/blog/2020/add-device-support-to-xcode</id>
    <updated>2020-04-04T01:03:06.264285+00:00</updated>
    <published>2020-04-04T01:03:06.264285+00:00</published>
    <link href="https://ikyle.me/blog/2020/add-device-support-to-xcode" />
    <summary type="text">Quick guide to adding support for unsupported iOS versions to Xcode by manually adding the needed DeviceSupport files</summary>
    <content type="html">&lt;p&gt;The latest version of iOS (13.4) was recently released, and with it the new version of Xcode. This new version of Xcode only runs on macOS Catalina. If, like me, you have been avoiding Catalina as much as possible this is a problem, because if you try to use the last version of Xcode that supports Mojave (Xcode 11.3) to Build and Run on a device that is running iOS 13.4 it will give you an error (as it only has support for iOS versions up to iOS 13.3).&lt;/p&gt;
&lt;p&gt;However, you can add extra iOS version support to Xcode by taking the DeviceSupport files from one version and adding them to another version (both to add support for newer, and older versions of iOS).&lt;/p&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/iGhibli/iOS-DeviceSupport/tree/master/DeviceSupport&#34;&gt;Find&lt;/a&gt; or download &lt;a href=&#34;https://developer.apple.com/download/more/?=xcode&#34;&gt;a version of Xcode&lt;/a&gt; that support the iOS version you want, in this case the latest one.&lt;/li&gt;
&lt;li&gt;Extract the Xcode.app bundle, navigate to &lt;code&gt;Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Take the folder for the iOS version you need and place it in the equivalent directory inside the version of Xcode you are running.&lt;/li&gt;
&lt;li&gt;At this point you&#39;ll need to restart Xcode at the minimum, though in my case I had to reboot completely.&lt;/li&gt;
&lt;li&gt;Now connect the iOS device running that iOS version and attempt to Build and Run to it.&lt;/li&gt;
&lt;li&gt;Xcode should not pull down the symbol files it needs from the device and you are good to go.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This will not give you the new SDK, but should allow you to continue using your existing Xcode for a while longer.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">View Remote Server Logs Locally using GoAccess</title>
    <id>https://ikyle.me/blog/2020/running-goaccess-locally</id>
    <updated>2020-04-02T03:33:41.875442+00:00</updated>
    <published>2020-04-02T03:33:41.875442+00:00</published>
    <link href="https://ikyle.me/blog/2020/running-goaccess-locally" />
    <summary type="text">How to use a locally installed copy of GoAccess to view a remote server&#39;s access logs</summary>
    <content type="html">&lt;p&gt;Since I removed Google Analytics I&#39;ve had 0 visibility into my websites traffic, and I&#39;d really like to at least see some stats like the page views.&lt;/p&gt;
&lt;p&gt;My website is a mix of Nginx serving static files and forwarding dynamic pages to a python web server using Flask.&lt;br /&gt;
After a bit of searching I realised I could just use Nginx&#39;s access logs directly, rather than introducing anything new. &lt;a href=&#34;https://goaccess.io&#34;&gt;GoAccess&lt;/a&gt; seemed to be the most recommended tool, but I wanted to avoid installing anything new on the server if I could help it, so I was really looking for a local tool.&lt;/p&gt;
&lt;p&gt;Fortunately, it is possible to run GoAccess on your local machine and view the server logs remotely over SSH.&lt;/p&gt;
&lt;h1&gt;How To&lt;/h1&gt;
&lt;p&gt;First make sure the server logs are accessible to the user you will be SSH&#39;ing in with, if they aren&#39;t then SSH in and fix the permissions first: &lt;code&gt;sudo chmod -R +r /var/log/nginx/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On macOS installing GoAccess is as simple as running:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;brew install goaccess
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then to view a single log file you can ssh into the server, cat the log file and pipe the output directly to GoAccess on your local machine.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh USER@IP_ADDRESS &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;cat /var/log/nginx/access.log&amp;#39;&lt;/span&gt; | goaccess -a --log-format&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After a while nginx will compress the log files and start again so you end up with a directory looking like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;access.log        access.log.2.gz   access.log.3.gz
access.log.4.gz   access.log.5.gz   access.log.6.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;There are now multiple log files, most of which are compressed.&lt;/p&gt;
&lt;p&gt;To view all the log files you can run &lt;a href=&#34;https://stackoverflow.com/a/39240021/458205&#34;&gt;this command&lt;/a&gt; to uncompress them, and concatenate them together before feeding them into GoAccess.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ssh USER@IP_ADDRESS &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;zcat -f /var/log/nginx/access.log*&amp;#39;&lt;/span&gt; | goaccess --log-format&lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;More details about actually using GoAccess to navigate the logs you have opened can be found on &lt;a href=&#34;https://goaccess.io/man#examples&#34;&gt;GoAccess&#39;s website&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Using UIImagePickerController on iOS</title>
    <id>https://ikyle.me/blog/2020/uiimagepickercontroller</id>
    <updated>2020-04-01T00:52:13.995134+00:00</updated>
    <published>2020-04-01T00:52:13.995134+00:00</published>
    <link href="https://ikyle.me/blog/2020/uiimagepickercontroller" />
    <summary type="text">How to use UIImagePickerController to select a photo from the Photo Library on iOS in Objective C and Swift</summary>
    <content type="html">&lt;p&gt;The simplest way to allow users to select a photo is to use the &lt;a href=&#34;https://developer.apple.com/documentation/uikit/uiimagepickercontroller&#34;&gt;&lt;code&gt;UIImagePickerController&lt;/code&gt;&lt;/a&gt; class, which provides a built in UI for users to access their photos.&lt;/p&gt;
&lt;p&gt;Doing so is incredibly simple.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Declare that your class conforms to the &lt;code&gt;UIImagePickerControllerDelegate&lt;/code&gt; and &lt;code&gt;UINavigationControllerDelegate&lt;/code&gt; protocols.&lt;/li&gt;
&lt;li&gt;Implement the &lt;code&gt;-imagePickerController:didFinishPickingMediaWithInfo:&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;Create and present an &lt;code&gt;UIImagePickerController&lt;/code&gt; view controller class.&lt;/li&gt;
&lt;li&gt;Do something with the image returned&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;- (&lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;pickPhoto&lt;/span&gt; {
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*imagePicker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;init&lt;/span&gt;];
    &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sourceType&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;UIImagePickerControllerSourceTypePhotoLibrary&lt;/span&gt;;
    &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;delegate&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;;
    [&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;presentViewController:imagePicker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;animated:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;YES&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;completion:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;];
}
&lt;span style=&#34;color: #177500&#34;&gt;// Implement UIImagePickerControllerDelegate method&lt;/span&gt;
-(&lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;imagePickerController:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;didFinishPickingMediaWithInfo:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;NSDictionary&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;lt;UIImagePickerControllerInfoKey&lt;/span&gt;,&lt;span style=&#34;color: #A90D91&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;{
    &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*image&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;UIImagePickerControllerOriginalImage&lt;/span&gt;];
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;imageView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt;;
    [&lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dismissViewControllerAnimated:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;YES&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;completion:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pickPhoto&lt;/span&gt;() {
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt;()
    &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;sourceType&lt;/span&gt; = .&lt;span style=&#34;color: #000&#34;&gt;photoLibrary&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;delegate&lt;/span&gt; = &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;present&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;animated&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;)
}
&lt;span style=&#34;color: #177500&#34;&gt;// Implement UIImagePickerControllerDelegate method&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;imagePickerController&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;didFinishPickingMediaWithInfo&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;InfoKey&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;])
    &lt;span style=&#34;color: #000&#34;&gt;imageView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;info&lt;/span&gt;[.&lt;span style=&#34;color: #000&#34;&gt;originalImage&lt;/span&gt;] &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt;? &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;picker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;dismiss&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;animated&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;completion&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;nil&lt;/span&gt;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Footnotes&lt;/h1&gt;
&lt;h2&gt;UIImagePickerControllerSourceType&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UIImagePickerControllerSourceTypePhotoLibrary&lt;/code&gt; (&lt;code&gt;.photoLibrary&lt;/code&gt;) allows users to pick from their full photo library, including browsing their albums.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UIImagePickerControllerSourceTypeSavedPhotosAlbum&lt;/code&gt; (&lt;code&gt;.savedPhotosAlbum&lt;/code&gt;) limits the selection to just their chronological &#39;Moments&#39; screen, camera roll.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UIImagePickerControllerSourceTypeCamera&lt;/code&gt; (&lt;code&gt;.camera&lt;/code&gt;) shows a camera interface so the user can take a picture their and then in the app.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; using &lt;code&gt;UIImagePickerControllerSourceTypeCamera&lt;/code&gt; (&lt;code&gt;.camera&lt;/code&gt;) will crash the app unless the info.plist contains the &lt;code&gt;NSCameraUsageDescription&lt;/code&gt; key with the following error message; unlike the other two, which use the act of the user selecting a photo as implied consent to give that photo too the app, and do not prompt for permission &lt;a href=&#34;https://stackoverflow.com/a/46594741/458205&#34;&gt;since iOS 11, when it was changed&lt;/a&gt; to run as a separate process (and so no longer require the &lt;code&gt;NSPhotoLibraryUsageDescription&lt;/code&gt; key).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;[access]&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;This&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;app&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;has&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;crashed&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;because&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;it&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;attempted&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;access&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;privacy-&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;sensitive&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;without&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;usage&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;description&lt;/span&gt;.
&lt;span style=&#34;color: #000&#34;&gt;The&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;app&amp;#39;s&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;Info&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;plist&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;must&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;contain&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;an&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;NSCameraUsageDescription&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;key&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;string&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;value&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;explaining&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;to&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;the&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;how&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;the&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;app&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;uses&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;this&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;data&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Select Photos, Videos or Both&lt;/h2&gt;
&lt;p&gt;By default &lt;code&gt;UIImagePickerController&lt;/code&gt; will only show and select images from a users photo library. However this is actually controlled via the &lt;code&gt;mediaTypes&lt;/code&gt; property, which acts as a filter. To select photos and videos you would set the array to include videos.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;@import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;MobileCoreServices&lt;/span&gt;; &lt;span style=&#34;color: #177500&#34;&gt;// where kUTTypeImage and kUTTypeMovie are defined&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;imagePicker&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;mediaTypes&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;NSString&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;kUTTypeImage&lt;/span&gt;, (&lt;span style=&#34;color: #5B269A&#34;&gt;NSString&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;kUTTypeMovie&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;MobileCoreServices&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;imagePicker.mediaTypes&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [(&lt;span style=&#34;color: #000&#34;&gt;kUTTypeImage&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;String&lt;/span&gt;), (&lt;span style=&#34;color: #000&#34;&gt;kUTTypeMovie&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;String&lt;/span&gt;)]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To extract the video you&#39;ll need to get the &lt;code&gt;UIImagePickerControllerMediaURL&lt;/code&gt; (&lt;code&gt;.mediaURL&lt;/code&gt;) key from the returning info dictionary, instead of the &lt;code&gt;UIImagePickerControllerOriginalImage&lt;/code&gt; (&lt;code&gt;originalImage&lt;/code&gt;) value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; The &lt;a href=&#34;https://developer.apple.com/documentation/uikit/uiimagepickercontroller/1619173-mediatypes&#34;&gt;developer documentation&lt;/a&gt; for the property includes this interesting comment regarding those Live Photos that you&#39;ve enabled effects like &#39;Bounce and Loop&#39; on.&lt;br /&gt;
&lt;code&gt;If you want to display a Live Photo rendered as a Loop or a Bounce, you must include the kUTTypeMovie identifier.&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Checking Capabilities&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;UIImagePickerController&lt;/code&gt; also includes several class methods to check availability of different features.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; ([&lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera&lt;/span&gt;]) {
    &lt;span style=&#34;color: #177500&#34;&gt;// This device has a camera&lt;/span&gt;
}
&lt;span style=&#34;color: #5B269A&#34;&gt;NSArray&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*mediaTypes&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera&lt;/span&gt;];
&lt;span style=&#34;color: #177500&#34;&gt;// Can this camera capture video, or just Photos (i.e: the first iPhone vs the iPhone 3G)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;isSourceTypeAvailable&lt;/span&gt;(.&lt;span style=&#34;color: #000&#34;&gt;camera&lt;/span&gt;) {
    &lt;span style=&#34;color: #177500&#34;&gt;// This device has a camera&lt;/span&gt;
}
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;mediaTypes&lt;/span&gt;:[&lt;span style=&#34;color: #A90D91&#34;&gt;String&lt;/span&gt;]? = &lt;span style=&#34;color: #5B269A&#34;&gt;UIImagePickerController&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;availableMediaTypes&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;camera&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// Can this camera capture video, or just Photos (i.e: the first iPhone vs the iPhone 3G)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Other&lt;/h2&gt;
&lt;p&gt;There are also more APIs, such as for getting the availability of flash, checking front and back cameras availability, checking the capabilities of each camera separately, and for allowing basic editing of the photo before it is given to your app. &lt;a href=&#34;https://developer.apple.com/documentation/uikit/uiimagepickercontroller&#34;&gt;Official UIImagePickerController Class Documentation&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Limitations&lt;/h1&gt;
&lt;p&gt;UIImagePickerController is an incredibly useful and convenient class for quickly adding image selection into an app. But it does have limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no multi-selection support&lt;/li&gt;
&lt;li&gt;limited filtering options (only photos, or videos, or both)&lt;/li&gt;
&lt;li&gt;no direct programmatic access to the photo library (only the photo you are given)&lt;/li&gt;
&lt;li&gt;limited camera access or UI customization.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want any of those features then you&#39;ll need to use &lt;code&gt;AVFoundation&lt;/code&gt; to make a custom camera UI, or &lt;code&gt;PhotoKit&lt;/code&gt; (which will be the subject of the next blog post) to make a custom image picker.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">QRCode Generation and Scanning on iOS 13</title>
    <id>https://ikyle.me/blog/2020/ios-qr-codes</id>
    <updated>2020-03-11T00:06:48.117661+00:00</updated>
    <published>2020-03-11T00:06:48.117661+00:00</published>
    <link href="https://ikyle.me/blog/2020/ios-qr-codes" />
    <summary type="text">How to generate a QR code image and how to scan a QR code with camera on iOS</summary>
    <content type="html">&lt;h1&gt;Generating QRCodes using CoreImage&lt;/h1&gt;
&lt;p&gt;iOS has built in support for generating &lt;a href=&#34;https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/uid/TP30000136-SW142&#34;&gt;several different&lt;/a&gt; types of 1D and 2D bar codes.&lt;/p&gt;
&lt;p&gt;The API in the &lt;code&gt;CoreImage&lt;/code&gt; framework used to generate these codes is &lt;code&gt;CIFilter&lt;/code&gt;, which returns a &lt;code&gt;CIImage&lt;/code&gt;. We can then turn this into a &lt;code&gt;UIImage&lt;/code&gt; to display on screen otherwise use.&lt;/p&gt;
&lt;p&gt;The filter is given an &lt;code&gt;NSData&lt;/code&gt; object as the input, and a string representing the amount of error correction to be used when making the QRCode (&lt;code&gt;L&lt;/code&gt; 7%, &lt;code&gt;M&lt;/code&gt; 15%, &lt;code&gt;Q&lt;/code&gt; 25%, or &lt;code&gt;H&lt;/code&gt; 30%).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;qrFilter&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;CIFilter&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;CIQRCodeGenerator&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// Text&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;qrFilter&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;setValue&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;text&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;data&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;using&lt;/span&gt;: .&lt;span style=&#34;color: #000&#34;&gt;isoLatin1&lt;/span&gt;), &lt;span style=&#34;color: #000&#34;&gt;forKey&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;inputMessage&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #177500&#34;&gt;// Error correction&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;qrFilter&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;setValue&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;M&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;forKey&lt;/span&gt;: &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;inputCorrectionLevel&amp;quot;&lt;/span&gt;)
&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;qrFilter&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;outputImage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;CIFilter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*qrFilter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;CIFilter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;filterWithName:&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;CIQRCodeGenerator&amp;quot;&lt;/span&gt;];
&lt;span style=&#34;color: #177500&#34;&gt;// Set the QR Code contents&lt;/span&gt;
[&lt;span style=&#34;color: #000&#34;&gt;qrFilter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;setValue:&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;qrString&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dataUsingEncoding:NSISOLatin1StringEncoding&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;forKey:&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;inputMessage&amp;quot;&lt;/span&gt;];
&lt;span style=&#34;color: #177500&#34;&gt;// Error correction&lt;/span&gt;
[&lt;span style=&#34;color: #000&#34;&gt;qrFilter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;setValue:&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;M&amp;quot;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;forKey:&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;inputCorrectionLevel&amp;quot;&lt;/span&gt;];
&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;qrFilter&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;outputImage&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;One small oddity is that, unlike might be expected, the string is not encoded with UTF8, but instead &lt;code&gt;ISO Latin 1&lt;/code&gt; string encoding.&lt;/p&gt;
&lt;h2&gt;Scaling&lt;/h2&gt;
&lt;p&gt;The resulting image is a QRCode, but is only as big as it needs to be so when you put it in your image view it will be scaled up and look blurry. So before using it you likely want to scale up the &lt;code&gt;CIImage&lt;/code&gt; to the size of the image view that it will be displayed in.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;qrCode&lt;/span&gt;:&lt;span style=&#34;color: #5B269A&#34;&gt;CIImage&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;createQRCode&lt;/span&gt;()
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewWidth&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;imageView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;size&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;scale&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;viewWidth/qrCode&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;extent&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;size&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;;
&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;scaledImage&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;qrCode&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;transformed&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;by&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;CGAffineTransform&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;scaleX&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;scale&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;y&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;scale&lt;/span&gt;))
&lt;span style=&#34;color: #000&#34;&gt;imageView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;ciImage&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;scaledImage&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;CIImage&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*qrCode&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;createQRForString:contents&lt;/span&gt;];
&lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewWidth&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;imageView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;size&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;;
&lt;span style=&#34;color: #000&#34;&gt;CGFloat&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;scale&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;viewWidth/qrCode&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;extent&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;size&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;width&lt;/span&gt;;
&lt;span style=&#34;color: #5B269A&#34;&gt;CIImage&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*scaledImage&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #000&#34;&gt;qrCode&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;imageByApplyingTransform:CGAffineTransformMakeScale&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;scale&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;scale&lt;/span&gt;)];
&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;imageView&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;image&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #5B269A&#34;&gt;UIImage&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;imageWithCIImage:scaledImage&lt;/span&gt;];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Reading QRCodes with AVFoundation Metadata&lt;/h1&gt;
&lt;p&gt;Reading a QRCode is similarly easy on iOS. When setting up a camera capture session (getting output from the camera) you can also attach various metadata outputs, for things such as faces, certain objects and QRCodes.&lt;/p&gt;
&lt;p&gt;After getting a configured &lt;code&gt;AVCaptureSession&lt;/code&gt; we can add our metadata output.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;metadataOutput&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;AVCaptureMetadataOutput&lt;/span&gt;()

&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;captureSession&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;canAddOutput&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;metadataOutput&lt;/span&gt;)) {
   &lt;span style=&#34;color: #000&#34;&gt;captureSession&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addOutput&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;metadataOutput&lt;/span&gt;)

   &lt;span style=&#34;color: #000&#34;&gt;metadataOutput&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;setMetadataObjectsDelegate&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;queue&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;DispatchQueue&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;)
   &lt;span style=&#34;color: #000&#34;&gt;metadataOutput&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;metadataObjectTypes&lt;/span&gt; = [.&lt;span style=&#34;color: #000&#34;&gt;qr&lt;/span&gt;]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #5B269A&#34;&gt;AVCaptureMetadataOutput&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*metadataOutput&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #5B269A&#34;&gt;AVCaptureMetadataOutput&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;init&lt;/span&gt;];

&lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; ([&lt;span style=&#34;color: #000&#34;&gt;captureSession&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;canAddOutput:metadataOutput&lt;/span&gt;]) {
    [&lt;span style=&#34;color: #000&#34;&gt;captureSession&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;addOutput:metadataOutput&lt;/span&gt;];

    [&lt;span style=&#34;color: #000&#34;&gt;metadataOutput&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;setMetadataObjectsDelegate:&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;queue:dispatch_get_main_queue&lt;/span&gt;()];
    &lt;span style=&#34;color: #000&#34;&gt;metadataOutput&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;metadataObjectTypes&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;@[&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;AVMetadataObjectTypeQRCode&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;]&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then we just need to implement &lt;code&gt;AVCaptureMetadataOutputObjectsDelegate&lt;/code&gt; and we have the QRCode&#39;s contents&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;metadataOutput&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;output&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;AVCaptureMetadataOutput&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;didOutput&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;metadataObjects&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;AVMetadataObject&lt;/span&gt;], &lt;span style=&#34;color: #000&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;connection&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;AVCaptureConnection&lt;/span&gt;) {
    &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;metadataObject&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;metadataObjects&lt;/span&gt;.&lt;span style=&#34;color: #5B269A&#34;&gt;first&lt;/span&gt; {
        &lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;readableObject&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;metadataObject&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt;? &lt;span style=&#34;color: #5B269A&#34;&gt;AVMetadataMachineReadableCodeObject&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; }
        &lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;stringValue&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;readableObject&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;stringValue&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; }

        &lt;span style=&#34;color: #5B269A&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;QR Code: \(&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;stringValue&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;)&amp;quot;&lt;/span&gt;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;- (&lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;captureOutput:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;AVCaptureOutput&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;captureOutput&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;didOutputMetadataObjects:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;NSArray&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;metadataObjects&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;fromConnection:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;AVCaptureConnection&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;connection&lt;/span&gt;{
    &lt;span style=&#34;color: #5B269A&#34;&gt;AVMetadataObject&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*metadata&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #000&#34;&gt;metadataObjects&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;firstObject&lt;/span&gt;];
    &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; ([&lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;isKindOfClass:&lt;/span&gt;[&lt;span style=&#34;color: #5B269A&#34;&gt;AVMetadataMachineReadableCodeObject&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt;]]) {
        &lt;span style=&#34;color: #5B269A&#34;&gt;NSString&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*stringValue&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [(&lt;span style=&#34;color: #5B269A&#34;&gt;AVMetadataMachineReadableCodeObject&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;metadata&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;stringValue&lt;/span&gt;];
        &lt;span style=&#34;color: #000&#34;&gt;NSLog&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;@&amp;quot;QR Code: %@&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;stringValue&lt;/span&gt;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can also ask the camera preview layer to translate the detected object into it&#39;s local coordinate system, allowing you to overlay something on the camera view that matches the QRCode&#39;s position.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;qrCodeObject&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;previewLayer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;transformedMetadataObject&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;readableObject&lt;/span&gt;)
&lt;span style=&#34;color: #000&#34;&gt;showQRCodeBounds&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;qrCodeObject&lt;/span&gt;?.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;QRCode Generator and Scanner Example Project&lt;/h1&gt;
&lt;p&gt;The example code in this blog post comes from an &lt;a href=&#34;https://github.com/kylehowells/QR-Code-Generator-and-Scanner&#34;&gt;example project on Github&lt;/a&gt;, which I wrote to demonstrate how to generate a QR code image and how to scan a QR code with the devices camera. (There is also a less feature complete &lt;a href=&#34;https://github.com/kylehowells/QR-Code-Demo-App/blob/master/QR%20Code%20Generator/ViewController.m&#34;&gt;ObjC example project&lt;/a&gt;)&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;GeneratorViewController.swift&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;ReaderViewController.swift&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/ios-qr-codes/Gen.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/ios-qr-codes/Scan.jpeg&#34; alt=&#34;&#34; /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Project Structure&lt;/h2&gt;
&lt;p&gt;The Xcode project uses the tab based application template, with the application and scene delegates unchanged from the Xcode template.&lt;br /&gt;
The 2 main files are &lt;a href=&#34;https://github.com/kylehowells/QR-Code-Generator-and-Scanner/blob/master/QR%20Codes/GeneratorViewController.swift&#34;&gt;&lt;code&gt;GeneratorViewController.swift&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://github.com/kylehowells/QR-Code-Generator-and-Scanner/blob/master/QR%20Codes/ReaderViewController.swift&#34;&gt;&lt;code&gt;ReaderViewController.swift&lt;/code&gt;&lt;/a&gt; and the code inside is very similar to the included example code in this blog post, just with the extra surrounding code needed to glue it together into a usable application.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">iOS Multiple Window Support Without Storyboards</title>
    <id>https://ikyle.me/blog/2020/ios-multi-window-support-without-storyboard</id>
    <updated>2020-03-10T23:18:48.695121+00:00</updated>
    <published>2020-03-10T23:18:48.695121+00:00</published>
    <link href="https://ikyle.me/blog/2020/ios-multi-window-support-without-storyboard" />
    <summary type="text">Supporting multiple windows and the UIScene lifecycle API programtically</summary>
    <content type="html">&lt;p&gt;If, like me, you prefer doing things programatically, instead of using interface builder, then the &lt;code&gt;Main.Storyboard&lt;/code&gt; file doesn&#39;t last beyond creating the new project.&lt;/p&gt;
&lt;p&gt;However, now with iOS 13 and multiple window there is now a &lt;code&gt;SceneDelegate&lt;/code&gt; as well as an &lt;code&gt;AppDelegate&lt;/code&gt; to deal with.&lt;br /&gt;
Fortunately, it isn&#39;t much more complex than before, just moved around slightly.&lt;/p&gt;
&lt;h1&gt;AppDelegate (iOS 12)&lt;/h1&gt;
&lt;p&gt;With iOS 12 you would setup an application programatically by deleting the &lt;code&gt;Main.Storyboard&lt;/code&gt; and then creating the window in the &lt;code&gt;AppDelegate&lt;/code&gt;&#39;s &lt;code&gt;application:willFinishLaunchingWithOptions:&lt;/code&gt; or &lt;code&gt;application:didFinishLaunchingWithOptions:&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;application&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;application&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIApplication&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;didFinishLaunchingWithOptions&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;launchOptions&lt;/span&gt;: [&lt;span style=&#34;color: #5B269A&#34;&gt;UIApplication&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;LaunchOptionsKey&lt;/span&gt;: &lt;span style=&#34;color: #A90D91&#34;&gt;Any&lt;/span&gt;]?) -&amp;gt; &lt;span style=&#34;color: #A90D91&#34;&gt;Bool&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIWindow&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;frame&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIScreen&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;main&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rootViewController&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;ViewController&lt;/span&gt;()
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;makeKeyAndVisible&lt;/span&gt;()
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;true&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;- (&lt;span style=&#34;color: #A90D91&#34;&gt;BOOL&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;application:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UIApplication&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;application&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;didFinishLaunchingWithOptions:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;NSDictionary&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;launchOptions&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #5B269A&#34;&gt;UIWindow&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;initWithFrame:&lt;/span&gt;[[&lt;span style=&#34;color: #5B269A&#34;&gt;UIScreen&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;mainScreen&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;bounds&lt;/span&gt;]];
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rootViewController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;ViewController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;init&lt;/span&gt;];
    [&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;makeKeyAndVisible&lt;/span&gt;];
    &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;YES&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;SceneDelegate (iOS 13)&lt;/h1&gt;
&lt;p&gt;The &lt;code&gt;info.plist&lt;/code&gt; file still has the &lt;code&gt;UIMainStoryboardFile&lt;/code&gt; key-value pair, but now it also has a &lt;code&gt;UISceneStoryboardFile&lt;/code&gt; key inside the &lt;code&gt;UIApplicationSceneManifest -&amp;gt; UISceneConfigurations&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;Once those 2 are deleted, the Storyboard is no longer involved in creating your apps UI and you need to do that in code yourself.&lt;br /&gt;
The location to do so is now in the &lt;code&gt;SceneDelegate&lt;/code&gt;&#39;s &lt;code&gt;-scene:willConnectToSession:options:&lt;/code&gt; method.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIScene&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;willConnectTo&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;session&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UISceneSession&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;options&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;connectionOptions&lt;/span&gt;: &lt;span style=&#34;color: #5B269A&#34;&gt;UIScene&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;ConnectionOptions&lt;/span&gt;) {
    &lt;span style=&#34;color: #177500&#34;&gt;// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;guard&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;windowScene&lt;/span&gt; = (&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;as&lt;/span&gt;? &lt;span style=&#34;color: #5B269A&#34;&gt;UIWindowScene&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt; { &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; }
    
    &lt;span style=&#34;color: #A90D91&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt; = &lt;span style=&#34;color: #5B269A&#34;&gt;UIWindow&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;windowScene&lt;/span&gt;: &lt;span style=&#34;color: #000&#34;&gt;windowScene&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rootViewController&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;ViewController&lt;/span&gt;()
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt; = &lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;makeKeyAndVisible&lt;/span&gt;()
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;- (&lt;span style=&#34;color: #A90D91&#34;&gt;void&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;scene:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UIScene&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;willConnectToSession:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UISceneSession&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;session&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;options:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UISceneConnectionOptions&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;connectionOptions&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #5B269A&#34;&gt;UIWindow&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;initWithWindowScene:&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;UIWindowScene&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color: #000&#34;&gt;scene&lt;/span&gt;];
    &lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;rootViewController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [[&lt;span style=&#34;color: #000&#34;&gt;ViewController&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;alloc&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;init&lt;/span&gt;];
    [&lt;span style=&#34;color: #A90D91&#34;&gt;self&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;window&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;makeKeyAndVisible&lt;/span&gt;];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The only difference compared to the old &lt;code&gt;-application:willFinishLaunchingWithOptions:&lt;/code&gt; code is that we now need to tell the &lt;code&gt;UIWindow&lt;/code&gt; which scene it is part of. This can be done using the new &lt;code&gt;UIWindow(windowScene: windowScene)&lt;/code&gt; init method, but windows can also be dynamically moved between scenes so there is also a &lt;code&gt;windowScene&lt;/code&gt; property on &lt;code&gt;UIWindow&lt;/code&gt; instances which can be changed at any time.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">Automatically Correct Insta360 Studio Snapshot Dates</title>
    <id>https://ikyle.me/blog/2020/correct-insta360-one-x-snapshot-date</id>
    <updated>2020-02-14T21:23:47.172803+00:00</updated>
    <published>2020-02-14T21:23:47.172803+00:00</published>
    <link href="https://ikyle.me/blog/2020/correct-insta360-one-x-snapshot-date" />
    <summary type="text">A simple script to fix the creation date of exported snapshots from Insta360 One X Studio</summary>
    <content type="html">&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/correct-insta360-one-x-snapshot-date/insta360-studio-1.jpg&#34; alt=&#34;Insta360 Studio 2019 for the Insta360 One X&#34; /&gt;&lt;/p&gt;
&lt;p&gt;When you import a 360º photo into the desktop Insta360 Studio software, one of the export options is to take a &#34;normal&#34; photo (called &#34;FreeCapture&#34; in Insta360&#39;s software, or &#34;Overcapture&#34; in the GoPro Fusion software) from the original 360º media, saving this as a snapshot.&lt;/p&gt;
&lt;p&gt;Unfortunately these snapshots are just that, exported snapshots of the preview area with no camera metadata.&lt;br /&gt;
Lack of metadata is especially annoying when sorting by date (as all phones do), as re-framed photos will not appear next to the 360º photos themselves, but next will appear as being from the day you processed it.&lt;/p&gt;
&lt;p&gt;Fortunately, the name includes all the date and time information needed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2020/correct-insta360-one-x-snapshot-date/before-filename.png&#34; alt=&#34;Image file save name suggestion IMG_20191230_120453_00_105_2020-02-14_17-17-37_screenshot.jpg&#34; /&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;IMG_20191230_120453_00_105_2020-02-14_17-17-37_screenshot.jpg

Pattern
IMG_|yr|mon|day|_|hr|min|sec|_00_#_[date screenshot saved]_screenshot.jpg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This means it is easy to create a simple script to fix the files after the fact, allowing you to use Insta360 Studio to snapshot lots of different angles of photos and video and then run a script to clean things up afterwards (instead of manually fixing each image file as you go).&lt;/p&gt;
&lt;h1&gt;Date Correction Script&lt;/h1&gt;
&lt;p&gt;The script needs to follow these simple instructions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find all jpg&#39;s with &lt;code&gt;screenshot&lt;/code&gt; in the name.&lt;/li&gt;
&lt;li&gt;Extract the date from the file name.&lt;/li&gt;
&lt;li&gt;Modify the exif date taken data to match.&lt;/li&gt;
&lt;li&gt;Save the new metadata.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(Though there are other steps, like merging in some metadata from the original 360 file, which could be taken to improve it.)&lt;/p&gt;
&lt;h2&gt;The script&lt;/h2&gt;
&lt;p&gt;The only external library used is one to extract the metadata and modify it, &lt;a href=&#34;https://github.com/hMatoba/Piexif&#34;&gt;Piexif&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;os&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;re&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;datetime&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;piexif&lt;/span&gt;

&lt;span style=&#34;color: #000&#34;&gt;filepath&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;os.path.abspath&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;.&amp;quot;&lt;/span&gt;)

&lt;span style=&#34;color: #177500&#34;&gt;# Find files with `screenshot` in the name.&lt;/span&gt;
&lt;span style=&#34;color: #000&#34;&gt;files&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #000&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;os.listdir&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;filepath&lt;/span&gt;) &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;screenshot&amp;quot;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;f.endswith&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;.jpg&amp;quot;&lt;/span&gt;)]
&lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;files&lt;/span&gt;)

&lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;files&lt;/span&gt;:
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;)
    &lt;span style=&#34;color: #177500&#34;&gt;# Extract datetime&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;date_rex&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;r&amp;quot;(20\d\d)(\d\d)(\d\d)_(\d\d)(\d\d)(\d\d)&amp;quot;&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;date_ranges&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;re.search&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;date_rex&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;)
    &lt;span style=&#34;color: #177500&#34;&gt;# Date&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;year&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;date_ranges.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;1&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;month&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;date_ranges.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;2&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;day&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;date_ranges.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;3&lt;/span&gt;)
    &lt;span style=&#34;color: #177500&#34;&gt;# Time&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;hour&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;date_ranges.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;4&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;minute&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;date_ranges.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;5&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;second&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;date_ranges.group&lt;/span&gt;(&lt;span style=&#34;color: #1C01CE&#34;&gt;6&lt;/span&gt;)
    &lt;span style=&#34;color: #177500&#34;&gt;# date text&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;dt_text&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;f&amp;quot;{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;year&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}:{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;month&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}:{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;day&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;} {&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;hour&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}:{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;month&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}:{&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;second&lt;/span&gt;&lt;span style=&#34;color: #C41A16&#34;&gt;}&amp;quot;&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;dt_text&lt;/span&gt;)
    &lt;span style=&#34;color: #177500&#34;&gt;# Image file path&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;image_filepath&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;os.path.join&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;filepath&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;filename&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image_filepath&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;exif_dict&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;piexif.load&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;image_filepath&lt;/span&gt;)
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;exif_dict&lt;/span&gt;) &lt;span style=&#34;color: #177500&#34;&gt;# Before&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;exif_dict&lt;/span&gt;[&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;0th&amp;quot;&lt;/span&gt;][&lt;span style=&#34;color: #000&#34;&gt;piexif.ImageIFD.DateTime&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dt_text&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;exif_dict&lt;/span&gt;[&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;Exif&amp;quot;&lt;/span&gt;][&lt;span style=&#34;color: #000&#34;&gt;piexif.ExifIFD.DateTimeOriginal&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;dt_text&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;exif_dict&lt;/span&gt;) &lt;span style=&#34;color: #177500&#34;&gt;# After&lt;/span&gt;
    &lt;span style=&#34;color: #177500&#34;&gt;# Save new metadata&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;exif_bytes&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;piexif.dump&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;exif_dict&lt;/span&gt;)
    &lt;span style=&#34;color: #000&#34;&gt;piexif.insert&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;exif_bytes&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;image_filepath&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This python script should be safe to re-run multiple times on the same directory (although it could cut down on fileIO by comparing the existing date metadata to see if it is correct before overriding it).&lt;/p&gt;
&lt;p&gt;Example output from &lt;code&gt;piexif.load(img)&lt;/code&gt; before and after:&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Before:
{&amp;#39;0th&amp;#39;: {}, &amp;#39;Exif&amp;#39;: {}, &amp;#39;GPS&amp;#39;: {}, &amp;#39;Interop&amp;#39;: {}, &amp;#39;1st&amp;#39;: {}, &amp;#39;thumbnail&amp;#39;: None}

After:
{&amp;#39;0th&amp;#39;: {306: &amp;#39;2019:12:30 12:04:53&amp;#39;}, &amp;#39;Exif&amp;#39;: {36867: &amp;#39;2019:12:30 12:04:53&amp;#39;}, &amp;#39;GPS&amp;#39;: {}, &amp;#39;Interop&amp;#39;: {}, &amp;#39;1st&amp;#39;: {}, &amp;#39;thumbnail&amp;#39;: None}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The script is also saved as a &lt;a href=&#34;https://gist.github.com/kylehowells/5ef3803d7acd3775ba611a6c5a6cee83&#34;&gt;gist on Github&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry xml:base="https://ikyle.me/blog/recent.xml">
    <title type="text">My New Website</title>
    <id>https://ikyle.me/blog/2017/my-new-website</id>
    <updated>2017-01-15T02:36:14.846980+00:00</updated>
    <published>2017-01-14T21:48:16.608110+00:00</published>
    <link href="https://ikyle.me/blog/2017/my-new-website" />
    <summary type="text">The design and architecture of my new website</summary>
    <content type="html">&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2017/my-new-website/homepage.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;Since I started learning programming I&#39;ve had a few different websites. I&#39;ve used Weebly, Wordpress and Tumblr. But I&#39;d always wanted to (and had never gotten around to) make my own website.&lt;br /&gt;
Now, finally, I have.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; The website is written in Python using &lt;a href=&#34;http://flask.pocoo.org&#34;&gt;Flask&lt;/a&gt;, running on an Ubuntu VPS from &lt;a href=&#34;https://m.do.co/c/44bf93142995&#34;&gt;DigitalOcean&lt;/a&gt; and the blog posts are written in &lt;a href=&#34;http://commonmark.org&#34;&gt;CommonMark&lt;/a&gt; (for now).&lt;br /&gt;
The front end is hand written HTML and CSS with minimal Javascript (2 main functions).&lt;/p&gt;
&lt;h2&gt;Visual Design&lt;/h2&gt;
&lt;p&gt;Before I started on writing any server side code, I created a few example pages to establish the visual style I wanted.&lt;/p&gt;
&lt;p&gt;I am not a designer. I am a programmer who likes good UI and wishes he was good at design.&lt;/p&gt;
&lt;p&gt;So to compensate, I look around for examples of design I like and save them. I started doing this with designs I particularly like on dribble and other sites.&lt;br /&gt;
At the time of writing this, my &#34;UI&#34; folder on my NAS contains 17 sub folders and 1089 images (yes, I should probably just use pinterest. I don&#39;t because I want them locally so I can run image/color analysis algorithms on them, if I ever get around to writing any that is...).&lt;/p&gt;
&lt;h3&gt;UI Prototypes&lt;/h3&gt;
&lt;p&gt;I&#39;m never sure how to start things. Whether it is a piece of writing, UI design or a website. I struggle to think of how to introduce things. One option I could have used was to simply have my website start on the blog index. However, I wanted multiple different sections to the website. Just as importantly I wanted them to feel like different sections, not simply set apart pages of the blog. The home page was inspired by &lt;a href=&#34;http://chpwn.com&#34;&gt;@chpwn&lt;/a&gt; and a few other developers (though the rest have all changed their websites&#39; designs).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2017/my-new-website/chpwn.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;For the rest of the site I used the top navigation bar (found on almost every website) and played around until I was happy with the design. Though I ended up tweaking things here and there anyway when I came to implement it properly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2017/my-new-website/example_design.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;If you are wondering about the example blog post, the title is obviously nonsense but, rather than use Lorem Ipsum for the body text, I used &lt;a href=&#34;https://www.pacifict.com/Story/&#34;&gt;this incredible story&lt;/a&gt; about the Graphing Calculator Apple bundled with the original PowerPC computers.&lt;/p&gt;
&lt;h3&gt;The Final Designs&lt;/h3&gt;
&lt;p&gt;I added a drop shadow to the navigation bar, vertically centred the home page and went back on my decision to let the content pages (such as the portfolio project pages and blog posts) go full width no matter the screen width. Overall though I kept things mostly the same.&lt;/p&gt;
&lt;p&gt;For the projects themselves I didn&#39;t actually decide on the design until I started writing the actual site. I originally planned for each of the projects to get a custom design specific to that project. That plan ended when I realised I don&#39;t have enough imagination to pull off more than 10 custom designs, each for incredibly similar projects.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2017/my-new-website/project_design.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;In the end I created a template I can customise to different projects and alter slightly for different types of projects. The app pages get the app icon and a link to the AppStore. The other types of projects simply get links to whatever official website the project has.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2017/my-new-website/project_design2.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;I spent a lot of time finding personal websites I liked the designs of and then looking at, and comparing, the “about me” pages. In the end what I ended up writing turned into a mini-portfolio page on its own. I’m not completely happy with the design but I’ll keep working on it over time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2017/my-new-website/about_me.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;I had originally planned to have the blog posts go full width across the browser window. However, I made this decision while using a 13&#34; MacBook Pro as my main computer. In the interim, between deciding that and implementing it, I switched to using a desktop computer with extra wide screen monitor and reconsidered my earlier position.&lt;br /&gt;
For overall style I tried to emulate the look of Github’s README files.&lt;/p&gt;
&lt;p&gt;A feature I particularly like on Medium, that lots of writing applications have (but very few websites), is the estimated reading time they display right at the top of the page. As soon as I load the article I know roughly how long I need, and if I should set it aside to read later.&lt;br /&gt;
If it is within the last week, the published, and &lt;em&gt;modified&lt;/em&gt; (I believe both should be shown on blog posts) labels display how long ago that event was, instead of the actual date. However, the actual date and time is always included in the ‘title’ attribute. Hovering over the relative timestamps should prompt the browser to display the real time stamp.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2017/my-new-website/blog_post.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;For categorising posts I decided to use &lt;em&gt;tags&lt;/em&gt; instead of categories. In my mind categories are like folders. A post can be put inside a single category. Whereas, multiple tags can be attached to a single article. So if I write about releasing a new iOS fitness app I can tag it with: iOS, fitness, press release, etc...&lt;br /&gt;
Later I can lookup all the iOS related blog posts I’ve written, or every press release, etc...&lt;/p&gt;
&lt;h2&gt;Code Design and Implementation&lt;/h2&gt;
&lt;h3&gt;Project Structure&lt;/h3&gt;
&lt;p&gt;The public facing web server itself is Nginx, running on an Ubuntu VPS from &lt;a href=&#34;https://m.do.co/c/44bf93142995&#34;&gt;DigitalOcean&lt;/a&gt;. Nginx is set up to try to serve files in the following order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The static resources directory.&lt;/li&gt;
&lt;li&gt;The static pages directory.&lt;/li&gt;
&lt;li&gt;The cache directory.&lt;/li&gt;
&lt;li&gt;Finally it will forward the request to the python web app.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This setup minimises the requests that make it to the python application and allows me to easily cache blog posts, and any other pages I want to be dynamic some of the time.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;server&lt;/span&gt; {
    &lt;span style=&#34;color: #A90D91&#34;&gt;listen&lt;/span&gt;       &lt;span style=&#34;color: #1C01CE&#34;&gt;80&lt;/span&gt;;
    &lt;span style=&#34;color: #A90D91&#34;&gt;server_name&lt;/span&gt;  &lt;span style=&#34;color: #C41A16&#34;&gt;ikyle.me&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;www.ikyle.me&lt;/span&gt;;
    &lt;span style=&#34;color: #A90D91&#34;&gt;charset&lt;/span&gt;      &lt;span style=&#34;color: #C41A16&#34;&gt;utf-8&lt;/span&gt;;

    &lt;span style=&#34;color: #A90D91&#34;&gt;root&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;/var/www/ikyle&lt;/span&gt;;
    &lt;span style=&#34;color: #A90D91&#34;&gt;index&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;index.html&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;index.htm&lt;/span&gt;;

    &lt;span style=&#34;color: #A90D91&#34;&gt;location&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;/&lt;/span&gt; {
        &lt;span style=&#34;color: #A90D91&#34;&gt;try_files&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;/static_files&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;$uri&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;/static_files&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;$uri/index.html&lt;/span&gt;
        &lt;span style=&#34;color: #C41A16&#34;&gt;/static_pages&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;$uri&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;/static_pages&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;$uri/index.html&lt;/span&gt;
        &lt;span style=&#34;color: #C41A16&#34;&gt;/cache&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;$uri&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;/cache&lt;/span&gt;&lt;span style=&#34;color: #000&#34;&gt;$uri/index.html&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;@blog&lt;/span&gt;;
    }

    &lt;span style=&#34;color: #A90D91&#34;&gt;location&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;@blog&lt;/span&gt; {
        &lt;span style=&#34;color: #A90D91&#34;&gt;include&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;uwsgi_params&lt;/span&gt;;
        &lt;span style=&#34;color: #A90D91&#34;&gt;uwsgi_pass&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;unix:/var/www/ikyle/ikyle_uwsgi.sock&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;One optimisation I have been thinking about doing is to: move the cache in front of the static files directory; extend the python application to monitor for changes to css and javascript files inside the static directory; and have it automatically run a minifier on those files, placing the output in the cache directory.&lt;/p&gt;
&lt;p&gt;I don’t know what the conventions are for structuring Python applications, but this is the file structure I went with.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Root
+-- error_pages/
|   Static error pages (404, etc...) are stored in here
+-- static_files/
|   Static files (css, js, images, etc...) go in here (can be cached)
+-- static_pages/
|   Static pages from the website (such as the about me and portfolio pages) go in here. (not to be cached)
|
+-- cache/
|   Rendered blog posts, etc go in here.
|
|
+-- portfolio/
|     +-- input/
|     |     +-- categories.json
|     |     |   Defines the different categories of portfolio projects, and their order
|     |     *-- &amp;#39;folder_name&amp;#39;/
|     |         Projects each have their own folder
|     |           +-- media/
|     |           |   This folder contains all the images and videos for the project
|     |           +-- info.json
|     |           |   This file defines everything about the project
|     |           *-- &amp;#39;file&amp;#39;.md
|     |               Any markdown files will be rendering into HTML files and placed as directories to the project itself
|     |
|     +-- templates/
|     |   The templates for the porfolio pages go in here
|     |
|     `-- renderer.py
|         Processes the input files and generates the resulting HTML pages
|
|
+-- blog.db
|   The SQLite file that acts as the websites database.
+-- app.py
|   Application entry point
+-- api/
|     +-- api.py
|     |   The python api through which the websites posts are accessed and searched
|     `-- utility.py
|         Functions shared across different pages, such as rendering Markdown and caching pages.
`-- web/
      +-- templates/
      |   The html templates that generate the web pages.
      |     +-- blog/
      |         The folder the blog templates are stored
      |           `-- admin/
      |               The folder the admin templates are stored
      |
      +-- webapi.py
      |   The public facing json web api endpoint.
      `-- blog.py
          The public html blog
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The top few directories are simply to place HTML, CSS and other static files in. The next part is the portfolio directory. This acts as its own subproject, separate from the rest of the application.&lt;/p&gt;
&lt;p&gt;As most of my projects are very similar (predominantly being iOS apps) I decided that I would create a template, like I had set up for the blog posts.&lt;br /&gt;
To create a new project page I create a new directory for it. Inside that directory I create an &lt;code&gt;info.json&lt;/code&gt; file and fill out the relevant information, such as: category; title; description; description; any press or reviews it has received; relevant images and videos; and links to related web pages.&lt;br /&gt;
The &lt;code&gt;rendered.py&lt;/code&gt; script takes the information from the &lt;code&gt;info.json&lt;/code&gt; files, passes it into the template and saves the result, along with the media, into the &lt;code&gt;static_pages&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;At the moment, I just run this locally on my computer and sync the resulting files onto the server. Adding a new project to my portfolio isn’t something I do regularly enough for me to have created a web UI for this yet.&lt;/p&gt;
&lt;p&gt;Finally, the last chunk is the actual Flask application itself.&lt;/p&gt;
&lt;h3&gt;The Flask Application&lt;/h3&gt;
&lt;p&gt;The application is separated into 3 sections: the &lt;code&gt;api&lt;/code&gt;; the &lt;code&gt;blog&lt;/code&gt;; and the &lt;code&gt;webapi&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;api&lt;/code&gt; directory contains a utilities file, with functions I use throughout the application, and the &lt;code&gt;api.py&lt;/code&gt; file that handles all database access for blog posts.&lt;/p&gt;
&lt;p&gt;As you might have noticed in the file directory structure, I use SQLite for the database. Before you start, yes I know.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://www.sqlite.org/about.html&#34;&gt;Think of SQLite not as a replacement for Oracle but as a replacement for fopen()&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, the reasons I choose to use SQLite are as follows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My dataset is small.&lt;br /&gt;
The dataset is my blog posts. Even after years of active blogging, we are only talking a few thousand text documents.&lt;/li&gt;
&lt;li&gt;My expected load is minimal.&lt;br /&gt;
As much as I’d like to imagine otherwise, my posts are unlikely to generate very much traffic.&lt;/li&gt;
&lt;li&gt;I’m lazy.&lt;br /&gt;
Choosing SQLite gave me: a quick and easy setup on the web server; the ability to run the website locally on OS X with minimal fuss; trivial backups; and &lt;a href=&#34;https://www.sqlite.org/fts3.html&#34;&gt;easy full text search&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having said that, I am reconsidering the way I handle storing blog posts, which I’ll detail more in the possible changes section at the end of this post.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;api.py&lt;/code&gt; file handles opening, closing and querying the database. Posts are returned as a custom &lt;code&gt;Dict&lt;/code&gt; subclass that allows you to access keys like attributes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;# Convience Dict Subclass&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;#  Allows dict[&amp;quot;name&amp;quot;] to be queried via dict.name&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #3F6E75&#34;&gt;DBItem&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;dict&lt;/span&gt;):
    &lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;__getattr__&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt;):
        &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;:
            &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt;]
        &lt;span style=&#34;color: #A90D91&#34;&gt;else&lt;/span&gt;:
            &lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;__setattr__&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;value&lt;/span&gt;):
        &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt;] &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;value&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;__delattr__&lt;/span&gt;(&lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt;):
        &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;:
            &lt;span style=&#34;color: #A90D91&#34;&gt;del&lt;/span&gt; &lt;span style=&#34;color: #5B269A&#34;&gt;self&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;name&lt;/span&gt;]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The biggest part of the file is the &lt;code&gt;get_posts()&lt;/code&gt; function. Rather than using several different functions to query different things, this takes several parameters, validates they are within allowed ranges, and builds the correct query string. Then it processes the result into an array of post objects.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #177500&#34;&gt;# Search posts&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;get_posts&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;query=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;sortDescending=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;before=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;after=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;summary=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;True&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;limit=&lt;/span&gt;&lt;span style=&#34;color: #1C01CE&#34;&gt;25&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;tags=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;):
    &lt;span style=&#34;color: #000&#34;&gt;...&lt;/span&gt;
&lt;span style=&#34;color: #177500&#34;&gt;# How many results does the search potentially return&lt;/span&gt;
&lt;span style=&#34;color: #A90D91&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;get_posts_count&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;query=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;before=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;after=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;tags=&lt;/span&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;None&lt;/span&gt;):
    &lt;span style=&#34;color: #000&#34;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This function is delicate enough that, if this was for something other than my personal blog, I would separate it into 3 distinct parts (the validation; query string building and result building) which could have tests run against them. However, setting up and writing tests for this, (and the different URL endpoints) isn’t worth it to me. If there is a bug I know exactly which file handles that part of the website and can deploy a fix within minutes.&lt;/p&gt;
&lt;p&gt;The 2 web facing parts of the website are split between &lt;code&gt;webapi.py&lt;/code&gt; and &lt;code&gt;blog.py&lt;/code&gt;, which are each separate &lt;a href=&#34;http://flask.pocoo.org/docs/0.12/blueprints/&#34;&gt;Flask Blueprints&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;app.py&lt;/code&gt; sets up Flask and adds my custom functions to the &lt;a href=&#34;http://jinja.pocoo.org/docs/2.9/&#34;&gt;jinja2&lt;/a&gt; template engine and registers the &lt;code&gt;webapi&lt;/code&gt; and &lt;code&gt;blog&lt;/code&gt; blueprints to the relevant &lt;code&gt;url_prefix&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;webapi.py&lt;/code&gt; is basically a thin JSON wrapper around the blog api.&lt;/p&gt;
&lt;p&gt;For example: &lt;code&gt;ikyle.me/blog/api/post?id=1&lt;/code&gt; will return the first blog post’s &lt;code&gt;Dict&lt;/code&gt; (which should be this post) dumped as a JSON file (including the content as markdown, rather than HTML).&lt;br /&gt;
This is the relevant code (just with some sanity checks removed).&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #000&#34;&gt;post&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;api.get_post&lt;/span&gt;( &lt;span style=&#34;color: #000&#34;&gt;request.args.get&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;id&amp;#39;&lt;/span&gt;) )
&lt;span style=&#34;color: #000&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;flask.make_response&lt;/span&gt;( &lt;span style=&#34;color: #000&#34;&gt;json.dumps&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;post&lt;/span&gt;) );
&lt;span style=&#34;color: #000&#34;&gt;r.mimetype&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt;;
&lt;span style=&#34;color: #A90D91&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I created the JSON api so that I have easy API access to my blog, in case I want to make an iOS app or something similar. However, the only endpoints that require authentication are the ones to create, modify or delete a blog post. The two needed for browsing are open.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;/blog/api/post?id=
/blog/api/search
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;blog.py&lt;/code&gt; is similar to &lt;code&gt;webapi.py&lt;/code&gt; but with the extras required for managing a website rather than an API endpoint. It queries the api, renders the posts, handles (most) server errors and generates the blog&#39;s &lt;a href=&#34;https://ikyle.me/blog/recent.xml&#34;&gt;Atom feed&lt;/a&gt; (yes it has one, I just haven’t added an icon linking to it yet).&lt;/p&gt;
&lt;p&gt;The Atom feed is generated by the &lt;a href=&#34;http://flask.pocoo.org/snippets/10/&#34;&gt;AtomFeed class from the Werkzeug contrib package&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also use &lt;a href=&#34;https://flask-login.readthedocs.io/en/latest/&#34;&gt;Flask-Login&lt;/a&gt; to manage access to a set of very basic admin features I built into the website. These consist of a basic blog post editor and buttons that appear allowing me to create, edit or delete posts.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2017/my-new-website/blog_editor.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;As I (for the moment) use unmodified &lt;a href=&#34;http://commonmark.org&#34;&gt;CommonMark&lt;/a&gt;, I am able to use the &lt;a href=&#34;https://github.com/jgm/commonmark.js&#34;&gt;JavaScript reference implementation&lt;/a&gt; to locally generate a live preview of the post as I am writing it. This is almost identical to the &lt;a href=&#34;http://spec.commonmark.org/dingus/&#34;&gt;commonmark.js dingus&lt;/a&gt;. One of the extra things I need to do is refresh the syntax highlighting.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;commonmark&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;window&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;commonmark&lt;/span&gt;;
&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;writer&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;commonmark&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;HtmlRenderer&lt;/span&gt;();
&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;htmlwriter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;commonmark&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;HtmlRenderer&lt;/span&gt;();
&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;xmlwriter&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;commonmark&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;XmlRenderer&lt;/span&gt;();
&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;reader&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;commonmark&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;Parser&lt;/span&gt;();

&lt;span style=&#34;color: #A90D91&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;debounce&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;func&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;wait&lt;/span&gt;, &lt;span style=&#34;color: #000&#34;&gt;immediate&lt;/span&gt;){ &lt;span style=&#34;color: #177500&#34;&gt;/* https://davidwalsh.name/javascript-debounce-function */&lt;/span&gt;}

&lt;span style=&#34;color: #A90D91&#34;&gt;document&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;DOMContentLoaded&amp;quot;&lt;/span&gt;, &lt;span style=&#34;color: #A90D91&#34;&gt;function&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;event&lt;/span&gt;) {
    &lt;span style=&#34;color: #000&#34;&gt;hljs&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;configure&lt;/span&gt;({&lt;span style=&#34;color: #000&#34;&gt;languages:&lt;/span&gt; []}); &lt;span style=&#34;color: #177500&#34;&gt;// Don&amp;#39;t auto detect languages&lt;/span&gt;
    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;article_text&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;document&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;getElementById&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;article_textarea&amp;quot;&lt;/span&gt;);
    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;preview_holder&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;document&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;getElementById&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;article_preview_div&amp;quot;&lt;/span&gt;);

    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;render_markdown&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;debounce&lt;/span&gt;(&lt;span style=&#34;color: #A90D91&#34;&gt;function&lt;/span&gt;() {
        &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;parsed&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;reader&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;parse&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;article_text&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;value&lt;/span&gt;);
        &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;renderedHTML&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;writer&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;render&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;parsed&lt;/span&gt;);

        &lt;span style=&#34;color: #000&#34;&gt;preview_holder&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;innerHTML&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;renderedHTML&lt;/span&gt;;
        &lt;span style=&#34;color: #000&#34;&gt;highlight_code&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;preview_holder&lt;/span&gt;);
    }, &lt;span style=&#34;color: #1C01CE&#34;&gt;250&lt;/span&gt;);

    &lt;span style=&#34;color: #000&#34;&gt;article_text&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;onchange&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;function&lt;/span&gt;() { &lt;span style=&#34;color: #000&#34;&gt;render_markdown&lt;/span&gt;(); };
    &lt;span style=&#34;color: #000&#34;&gt;article_text&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;oninput&lt;/span&gt;  &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #A90D91&#34;&gt;function&lt;/span&gt;() { &lt;span style=&#34;color: #000&#34;&gt;render_markdown&lt;/span&gt;(); };
    &lt;span style=&#34;color: #177500&#34;&gt;// Call them once at the start&lt;/span&gt;
    &lt;span style=&#34;color: #000&#34;&gt;render_markdown&lt;/span&gt;();
}

&lt;span style=&#34;color: #A90D91&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;highlight_code&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;element&lt;/span&gt;) {
    &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;elements&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;element&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;getElementsByTagName&lt;/span&gt;(&lt;span style=&#34;color: #C41A16&#34;&gt;&amp;#39;pre&amp;#39;&lt;/span&gt;);
    &lt;span style=&#34;color: #A90D91&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;elements&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;length&lt;/span&gt;; &lt;span style=&#34;color: #000&#34;&gt;i++&lt;/span&gt;) {
        &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pre_block&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;elements&lt;/span&gt;[&lt;span style=&#34;color: #000&#34;&gt;i&lt;/span&gt;];
        &lt;span style=&#34;color: #A90D91&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;possible_block&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;pre_block&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;children&lt;/span&gt;[&lt;span style=&#34;color: #1C01CE&#34;&gt;0&lt;/span&gt;];
        &lt;span style=&#34;color: #A90D91&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #000&#34;&gt;possible_block&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;tagName&lt;/span&gt; &lt;span style=&#34;color: #000&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color: #C41A16&#34;&gt;&amp;quot;CODE&amp;quot;&lt;/span&gt;) {
            &lt;span style=&#34;color: #000&#34;&gt;hljs&lt;/span&gt;.&lt;span style=&#34;color: #000&#34;&gt;highlightBlock&lt;/span&gt;(&lt;span style=&#34;color: #000&#34;&gt;possible_block&lt;/span&gt;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Future Changes&lt;/h2&gt;
&lt;p&gt;I’m happy I have the site at a point I can push it live. However, this is not how the website will always be. Even now, there are lots of things I want to change.&lt;/p&gt;
&lt;p&gt;At the moment, the website isn’t optimised very much. I designed &lt;strong&gt;a cache&lt;/strong&gt; directory into the design from the beginning, but don’t use it at present.&lt;/p&gt;
&lt;p&gt;Both the &lt;em&gt;blog posts&lt;/em&gt; and &lt;em&gt;atom feed&lt;/em&gt; are dynamically generated on every request. An obvious gain would be to cache both of these.&lt;/p&gt;
&lt;p&gt;The atom feed could be saved and only regenerated every time a blog post is changed, rather than on every request.&lt;/p&gt;
&lt;p&gt;Blog posts have friendly dynamic timestamps, but only for the first week (which is also the time a post is most likely to be edited). Thereafter, they could be cached indefinitely, only needing to be regenerated if modified. Alternatively, the friendly timestamps could be generated locally with javascript, allowing the posts to be cached from the start.&lt;/p&gt;
&lt;p&gt;I am currently writing this post using &lt;a href=&#34;https://ia.net/writer/&#34;&gt;iA Writer&lt;/a&gt; on my MacBook, rather than in the post editor I spent the time to make, because I didn’t think to add the ability to save (or even the concept of) &lt;strong&gt;drafts&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Using CommonMark has its advantages. I can use &lt;a href=&#34;https://github.com/jgm/CommonMark/wiki/List-of-CommonMark-Implementations&#34;&gt;any CommonMark compatible renderer&lt;/a&gt; unmodified and my blog posts will render correctly.&lt;br /&gt;
Despite this, I don’t think my blog will use standard CommonMark for very long.&lt;/p&gt;
&lt;p&gt;One missing feature I was shocked to find out isn’t part of either Markdown or CommonMark is footnotes. I was particularly surprised given that John Gruber is the &lt;a href=&#34;https://daringfireball.net/projects/markdown/&#34;&gt;original author of Markdown&lt;/a&gt; and his website, &lt;a href=&#34;https://daringfireball.net&#34;&gt;daringfireball.net&lt;/a&gt;, uses footnotes in almost every post.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;This should be a footnote.[^comment]

[&lt;span style=&#34;color: #000&#34;&gt;^comment&lt;/span&gt;]: &lt;span style=&#34;color: #836C28&#34;&gt;Sadly this syntax isn&amp;#39;t supported for some reason.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I would have used this feature extensively while writing this article. Almost everywhere I have included a comment in (brackets), and some of the comments blocked by commas, would have instead been a footnote. I left out a lot of small related comments I have about different sections of this post because I would have put them in a footnote, and adding them into the main body of the text would divert that section off course too far from its original topic.&lt;/p&gt;
&lt;p&gt;If I’m going to &lt;strong&gt;give up compatibility with the CommonMark spec&lt;/strong&gt; I might as well go all the way. Other non-standard extensions I’d like include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#tables&#34;&gt;Tables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments&#34;&gt;Checkboxes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/cmrd-senya/markdown-it-html5-embed&#34;&gt;The image syntax extended to include videos automatically&lt;/a&gt;, based on the file extension.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;John Gruber has stated that for things Markdown doesn’t include you should use HTML directly. However, one of my reasons for using Markdown is to avoid using HTML directly.&lt;br /&gt;
Keep in mind that making a native iOS app is always faintly at the back of my mind, even if I end up never getting around to it. Including HTML in your markdown might be fine if the markdown is just going to be rendered out to HTML. But, if the result is going to be rendered differently (by iOS’s text rendering engine for example) HMTL is just going to get in the way and cause you issues.&lt;/p&gt;
&lt;p&gt;Talking about custom changes to Markdown leads me neatly into the next point. I will, probably, completely change the way blog posts are handled.&lt;/p&gt;
&lt;p&gt;At the moment, blog posts are stored in the SQLite database, and blog post’s images are handled... well that’s another feature, like drafts, I didn’t realise I needed to handle until I started writing this blog post. I manually rsync’d the images for &lt;em&gt;this post&lt;/em&gt; onto the server.&lt;/p&gt;
&lt;p&gt;An article I read a little while ago which really caught my imagination, and that I haven’t seen mentioned much online, was this blog post announcing &lt;a href=&#34;https://ia.net/writer/blog/ia-writer-4/&#34;&gt;iA Writer 4&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In it they mentioned a quote from John Gruber about Markdown’s image syntax (which I keep forgetting).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My best idea for good Markdown img syntax would be to just paste in a URL ending in .jpg/.png/.gif etc.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve already mentioned I think the image syntax should be expanded to include .mp4, .mp3, etc... However, iA Labs took the idea a step further. This allows tables, source code and even other bits of text to be separated into external csv, source and text files.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://ikyle.me/blog/2017/my-new-website/iA_Writer_4.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;
&lt;p&gt;They even have an example spec for &lt;a href=&#34;https://github.com/iainc/Markdown-Content-Blocks&#34;&gt;&lt;strong&gt;file transclusion&lt;/strong&gt;&lt;/a&gt;, as the concept is named.&lt;/p&gt;
&lt;p&gt;My revised &lt;strong&gt;handling of blog posts&lt;/strong&gt; would look much more like iA Writer’s handling of its library, or even how I handle the portfolio, than it looks at the moment.&lt;/p&gt;
&lt;div class=&#34;highlight&#34; style=&#34;background: #ffffff&#34;&gt;&lt;pre style=&#34;line-height: 125%;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Root
+-- blog/
|     +-- posts/
|     |     *-- &amp;quot;year&amp;quot;/
|     |     |   Each year with its own directory.
|     |     |     *-- &amp;quot;post_title&amp;quot;/
|     |     |     |   Each post has its own directory
|     |     |     +-- info.json
|     |     |     |   This file defines the title, description, author, tags, and publish/modification date
|     |     |     +-- post.md
|     |     |     |   This would be the custom CommonMark for the post itself.
|     |     |     `-- resources/
|     |     |     |   This directory would be contain the images, text files and other files referenced in the post.md file.  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I considered including the metadata at the top of the post.md file, like &lt;a href=&#34;https://github.com/NSHipster/articles&#34;&gt;some websites do&lt;/a&gt;, but I prefer a slightly more structured layout.&lt;/p&gt;
&lt;p&gt;Ideally, the python application would monitor the &lt;code&gt;posts&lt;/code&gt; directory for changes and delete the cached HTML for the relevant blog post when needed. Then, after a short delay it’d re-render the post’s cached HTML file.&lt;/p&gt;
&lt;p&gt;I would still use the SQLite database. However, instead of being the primary location for posts to be stored it would mirror the tags, metadata and the full text of blog posts. Rather than loading in the markdown directly, I’d parse the markdown and extract just the text (in much the same way I currently generate the post’s word count) loading that into the database, to make use of the SQLite full text search.&lt;/p&gt;
&lt;p&gt;Other items I have notes to add in the future include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Image uploading from my post editor&lt;/li&gt;
&lt;li&gt;A better admin page&lt;/li&gt;
&lt;li&gt;A web based portfolio project creation page/editor&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;I’m never completely satisfied with anything I make, there’s always another feature I could add, a better way to do something, a more efficient way to achieve what I already have.&lt;/p&gt;
&lt;p&gt;However, I am proud of this website.&lt;/p&gt;
&lt;p&gt;I have done other websites before, but they all used existing templates, a ready made CMS or were just a single static HTML page. This was my first real server side project, and one of my first attempts at doing the design for a larger project myself.&lt;/p&gt;
</content>
  </entry>
</feed>
